library(pagoda2)
Loading required package: Matrix
Loading required package: igraph
package ‘igraph’ was built under R version 3.5.3
Attaching package: ‘igraph’
The following objects are masked from ‘package:stats’:
decompose, spectrum
The following object is masked from ‘package:base’:
union
library(conos)
Attaching package: ‘conos’
The following objects are masked _by_ ‘.GlobalEnv’:
plotClusterBarplots, plotComponentVariance, plotDEheatmap
library(Matrix)
library(parallel)
library(cowplot)
Loading required package: ggplot2
Attaching package: ‘cowplot’
The following object is masked from ‘package:ggplot2’:
ggsave
Load original conos object and annotations
scon <- Conos$new(readRDS("~pkharchenko/m/scadden/bmmet/jan2019/scon.rds"))
load("~pkharchenko/m/scadden/bmmet/jan2019/annotations.RData")
Look at the coarse annotation:
alpha <- 0.1; size <- 0.2;
p1 <- scon$plotGraph(groups=typefc,palette=typefc.pal,alpha=alpha,size=size,mark.groups=T,plot.na=F)
p1

Look at the detailed annotation:
alpha <- 0.1; size <- 0.2;
p2 <- scon$plotGraph(groups=typef,alpha=alpha,size=size,mark.groups=T,plot.na=F)
p2


Doublets
source("~/m/pavan/DLI/conp2.r")
data.table 1.11.8 Latest news: r-datatable.com

alpha <- 0.1; size <- 0.2;
p1 <- scon$plotGraph(groups=typefc[doublet.f[names(typefc)]<=0.25],palette=typefc.pal,alpha=alpha,size=size,mark.groups=T,plot.na=F)
p1

Marker genes
Marker genes
nfac <- as.factor(typefc[doublet.f[names(typefc)]<=0.25])
annot.de <- scon$getDifferentialGenes(groups=nfac,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
#source("~/m/p2/conos/R/plot.R")
pp <- plotDEheatmap(scon,nfac,annot.de,n.genes.per.cluster = 20 ,show.gene.clusters=T,column.metadata=list(samples=scon$getDatasetPerCell()),order.clusters = T, column.metadata.colors = list(clusters=typefc.pal),use_raster = T,raster_device = "CairoPNG")
pdf(file='annot.heatmap.pdf',width=10,height=30); print(pp); dev.off();
null device
1
A small version of the hetmap, for the main figure
source("~/m/p2/conos/R/plot.R")
genes <- c('MS4A1','CD79A','CD79B','MZB1','VPREB3','SEC11C','IGLL5','GNLY','GZMB','LILRA4','AZU1','MPO','FCN1','IER3','C5AR1','IGJ','SOX4','STMN1','MYL9','HBD','GZMK','AR','KLK2')
pp <- plotDEheatmap(scon,nfac,annot.de,n.genes.per.cluster = 20 ,show.gene.clusters=T, column.metadata.colors = list(clusters=typefc.pal), order.clusters = T, additional.genes = genes, labeled.gene.subset = genes, min.auc = 0.6,use_raster = T,raster_device = "CairoPNG")
pp

alpha <- 0.5; size <- 0.1;
scon$plotGraph(gene='KRT3',alpha=alpha,size=size,mark.groups=F,plot.na=F)
Error in scon$plotGraph(gene = "KRT3", alpha = alpha, size = size, mark.groups = F, :
gene KRT3 is not found in any of the samples
Sample panel:
pp <- scon$plotPanel(groups=nfac,plot.na=F,alpha=0.3,size=0.5,use.common.embedding = F, ncol=4, mark.groups=T,palette=typefc.pal,raster=T, raster.width=3,raster.height=3,title.size=4,font.size=c(3,4))
#pdf('panel.pdf',height=8,width=2); print(pp); dev.off();
pp

Just the Tumor fractions
pl <- lapply(grep("Tumor",names(scon$samples),val=T),function(nam) {
sccore::embeddingPlot(ncon$samples[[nam]]$embeddings$PCA$tSNE,groups=nfac,palette=typefc.pal,plot.na=F,raster=T,raster.height=3,raster.width=3,font.size=c(3,4)) + ggtitle(nam) +theme_bw() + theme(legend.position = "none") + theme(axis.ticks = element_blank(), axis.title = element_blank(), axis.text = element_blank(),panel.grid.major = element_blank(), panel.grid.minor=element_blank())
})
plot_grid(plotlist=pl,nrow=3)

pl <- lapply(grep("Tumor",names(scon$samples),val=T),function(nam) {
sccore::embeddingPlot(ncon$samples[[nam]]$embeddings$PCA$tSNE,colors=conos:::getGeneExpression(ncon$samples[[nam]],'KLK2'),plot.na=F,raster=T,raster.height=3,raster.width=3,font.size=c(3,4)) + ggtitle(nam) +theme_bw() + theme(legend.position = "none") + theme(axis.ticks = element_blank(), axis.title = element_blank(), axis.text = element_blank(),panel.grid.major = element_blank(), panel.grid.minor=element_blank())
})
no non-missing arguments to min; returning Infno non-missing arguments to max; returning -Inf
plot_grid(plotlist=pl,nrow=3)

Expression difference magnitude analysis
An extended version, non-paired comparisons.
source("~pkharchenko/pavan/DLI/conp2.r")
cannot open file '/home/pkharchenko/pavan/DLI/conp2.r': No such file or directoryError in file(filename, "r", encoding = encoding) :
cannot open the connection
JS-based distances
rlist <- list("Tumor vs. Benign"=xTB,"Involved vs. Benign"=xIB,"Tumor vs. Involved"=xIT,"Tumor vs. Distal"=xTD,"Involved vs. Distal"=xID)
pl <- lapply(names(rlist),function(nam) {
ggplot(rlist[[nam]]$df,aes(x=as.factor(Type),y=value)) +
geom_boxplot(notch=T,outlier.shape=NA) +
geom_jitter(position=position_jitter(0.1), aes(color=patient),show.legend=FALSE,alpha=0.1) +
geom_hline(yintercept = 1,linetype=2,color='gray80') +
ylim(0,min(5,max(rlist[[nam]]$df$value)))+
theme(axis.text.x=element_text(angle = 90, hjust=1), axis.text.y=element_text(angle=90, hjust=0.5)) +
ggtitle(nam)+
labs(x="", y="normalized distance")
})
plot_grid(plotlist=pl,nrow=2)
notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
Removed 2 rows containing missing values (geom_point).notch went outside hinges. Try setting notch=FALSE.
Removed 2 rows containing missing values (geom_point).Removed 8 rows containing non-finite values (stat_boxplot).notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
Removed 8 rows containing missing values (geom_point).Removed 10 rows containing non-finite values (stat_boxplot).notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
Removed 10 rows containing missing values (geom_point).

Correlation-based figure
rlist <- list("Tumor vs. Benign"=xTBc,"Involved vs. Benign"=xIBc,"Tumor vs. Distal"=xTDc,"Involved vs. Distal"=xIDc)
pl <- lapply(names(rlist),function(nam) {
ggplot(rlist[[nam]]$df,aes(x=as.factor(Type),y=value)) +
geom_boxplot(notch=T,outlier.shape=NA) +
geom_jitter(position=position_jitter(0.1), aes(color=patient),show.legend=FALSE,alpha=0.1) +
geom_hline(yintercept = 1,linetype=2,color='gray80') +
#ylim(0,min(5,max(rlist[[nam]]$df$value)))+
ylim(0,5)+
theme(axis.text.x=element_text(angle = 90, hjust=1), axis.text.y=element_text(angle=90, hjust=0.5)) +
ggtitle(nam)+
labs(x="", y="standardized distance")
})
pp <- plot_grid(plotlist=pl,nrow=2)
Removed 6 rows containing non-finite values (stat_boxplot).notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
Removed 6 rows containing missing values (geom_point).notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
Removed 26 rows containing non-finite values (stat_boxplot).notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
Removed 26 rows containing missing values (geom_point).Removed 10 rows containing non-finite values (stat_boxplot).notch went outside hinges. Try setting notch=FALSE.
notch went outside hinges. Try setting notch=FALSE.
Removed 10 rows containing missing values (geom_point).
pdf(file='difference.magnitudes.pdf',width=7,height=8); print(pp); dev.off();
png
2
pp

Realignment of the Tumor fraction without translation/mitochondrial genes
p2l <- lapply(lapply(dl,function(x) x[,!doublet.f[colnames(x)]>0.25]), basicP2proc, n.cores=30, min.cells.per.gene=0,min.transcripts.per.cell=1e3, n.odgenes=2e3, get.largevis=FALSE, make.geneknn=FALSE)
Remove translation and respiratory chain genes that are bringing tumor cells close to progentiors
library(biomaRt)
ensembl = useMart("ensembl",dataset="hsapiens_gene_ensembl")
#gene.data <- getBM(attributes=c('hgnc_symbol'),filters = 'go', values = "GO:0000280", mart = ensembl)
#getBM(attributes = c('entrezgene_id','hgnc_symbol'), filters = 'go', values = 'GO:0000278', mart = ensembl)
#getBM(attributes = c('entrezgene_id','hgnc_symbol'), filters = 'go', values = 'GO:0006260', mart = ensembl)
tr.genes <- getBM(attributes = c('entrezgene_id','hgnc_symbol'), filters = 'go', values = c('GO:0006412','GO:0006414','GO:0022904','GO:0055114','GO:0070125','GO:0006839'), mart = ensembl)
tr.genes <- tr.genes$hgnc_symbol
saveRDS(tr.genes,file='tr.genes.rds')
tr.genes <- readRDS("tr.genes.rds")
p2l <- lapply(lapply(dl,function(x) x[!rownames(x) %in% tr.genes,!doublet.f[colnames(x)]>0.25]), basicP2proc, n.cores=30, min.cells.per.gene=0,min.transcripts.per.cell=1e3, n.odgenes=2e3, get.largevis=FALSE, make.geneknn=FALSE)
Conos integration of only tumor fractions:
ncon <- Conos$new(p2l[grepl("Tumor",names(p2l))],n.cores=30)
#ncon$buildGraph(k=15, k.self=5, space='CPCA', ncomps=30, n.odgenes=2000, verbose=TRUE)
#ncon$buildGraph(k=15, k.self=5, space='CCA', ncomps=30, n.odgenes=2000, verbose=TRUE)
ncon$buildGraph(k=15, k.self=5, space='PCA', ncomps=30, n.odgenes=2000, verbose=TRUE)
ncon$embedGraph(method='UMAP');
# store different embeddings
ncon$misc$embeddings <- list()
ncon$misc$embeddings$umap <- ncon$embedding
ncon$embedGraph(sgd_batches=2e8,apha=1.5);
ncon$misc$embeddings$lv <- ncon$embedding
ncon$findCommunities(method=leiden.community,resolution=1)
Take a look
alpha <- 0.1; size <- 0.5;
ncon$embedding <- ncon$misc$embeddings$umap
n1 <- ncon$plotGraph(groups=typefc,palette=typefc.pal,alpha=alpha,size=size,mark.groups=T,plot.na=F)
n2 <- ncon$plotGraph(groups=typef,alpha=alpha,size=size,mark.groups=T,plot.na=F,font.size=c(3,5))
#n3 <- ncon$plotGraph(groups=ncon$getDatasetPerCell(),alpha=alpha,size=size,mark.groups=F,plot.na=F)
n3 <- ncon$plotGraph(alpha=alpha,size=size,mark.groups=T,plot.na=F)
plot_grid(plotlist=list(p1,n1,n2,n3),nrow=2)

alpha <- 0.1; size <- 0.3;
n1 <- ncon$plotGraph(groups=typefc,palette=typefc.pal,alpha=alpha,size=size,mark.groups=T,plot.na=F,font.size=c(3,4),raster=T,raster.width=3,raster.height=3)
pdf(file='tumor.nometab.embedding.pdf',width=3,height=3); print(n1); dev.off();
null device
1
n1

alpha <- 0.2; size <- 0.1;
#n1 <- ncon$plotGraph(groups=typefc,palette=typefc.pal,alpha=alpha,size=size,mark.groups=T,plot.na=F)
n2 <- ncon$plotGraph(groups=typef,alpha=alpha,size=size,mark.groups=T,plot.na=F,font.size=c(3,4))
n3 <- ncon$plotGraph(gene='KLK2',alpha=alpha,size=size,mark.groups=F,plot.na=F)
plot_grid(plotlist=list(n2,n3),nrow=1)

Contrast tumor cluster against progenitor cluster 38
x <- ncon$clusters[[1]]$groups
tumor.cells <- names(x)[x==42]
prog.cells <- names(x)[x==38]
unfac <- typefc[doublet.f[names(typefc)]<=0.25]
unfac[names(unfac)%in% tumor.cells] <- 'Tumor'
unfac <- as.factor(unfac)
tfac <- ncon$getDatasetPerCell()
tfac <- tfac[!names(tfac) %in% prog.cells]
tfac <- as.factor(setNames(ifelse(names(tfac) %in% tumor.cells,'tumor','other'),names(tfac)))
tumor.de <- ncon$getDifferentialGenes(groups=tfac,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
pfac <- ncon$getDatasetPerCell()
pfac <- pfac[!names(pfac) %in% tumor.cells]
pfac <- as.factor(setNames(ifelse(names(pfac) %in% prog.cells,'prog','other'),names(pfac)))
prog.de <- ncon$getDifferentialGenes(groups=pfac,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
cfac <- ncon$getDatasetPerCell()
cfac <- as.factor(setNames(ifelse(names(cfac) %in% c(prog.cells,tumor.cells),'comb','other'),names(cfac)))
comb.de <- ncon$getDifferentialGenes(groups=cfac,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
# try with the entire progenitor population, balancing sizes
cfac <- ncon$getDatasetPerCell()
x <- names(typefc)[typefc=="Progenitors"];
xs <- sample(x,length(tumor.cells))
pfac <- pfac[!names(pfac) %in% setdiff(x,xs)]
cfac <- as.factor(setNames(ifelse(names(cfac) %in% c(xs,tumor.cells),'comb','other'),names(cfac)))
comb.de2 <- ncon$getDifferentialGenes(groups=cfac,n.cores=30,append.auc=TRUE,z.threshold=0,upregulated.only=T)
Top tumor markers
require(dplyr)
tde <- arrange(tumor.de$tumor,by=desc(AUC))
tde
pde <- arrange(prog.de$prog,by=desc(AUC))
pde
arrange(comb.de2$comb,by=desc(AUC))
write(head(comb.de2,100)$Gene,file='comb2.top100.txt',sep='\t',quote=F)
Error in write(head(comb.de2, 100)$Gene, file = "comb2.top100.txt", sep = "\t", :
unused argument (quote = F)

Look at the intersecting genes
tde <- arrange(tumor.de$tumor,by=desc(Z))
pde <- arrange(prog.de$prog,by=desc(Z))
alpha <- 0.1; size <- 0.2;
ncon$embedding <- ncon$misc$embeddings$umap
n1 <- scon$plotGraph(groups=unfac,palette=typefc.pal,alpha=alpha,size=size,mark.groups=T,plot.na=F)
n1

pl <- lapply(grep("Tumor",names(scon$samples),val=T),function(nam) {
sccore::embeddingPlot(ncon$samples[[nam]]$embeddings$PCA$tSNE,groups=unfac,palette=typefc.pal,plot.na=F,raster=T,raster.height=3,raster.width=3,font.size=c(3,4)) + ggtitle(nam) +theme_bw() + theme(legend.position = "none") + theme(axis.ticks = element_blank(), axis.title = element_blank(), axis.text = element_blank(),panel.grid.major = element_blank(), panel.grid.minor=element_blank())
})
plot_grid(plotlist=pl,nrow=3)

Look at Eli’s genes
qplot <- function(g, con.obj=con, ann=con$clusters$leiden$groups ) {
#cat(g,' ')
x <- lapply(con.obj$samples[!grepl("Whole|Noninv",names(con.obj$samples))],function(r) { if(g %in% colnames(r$counts)) { r$counts[,g] } else { return(NULL) } })
if(length(unlist(x))<1) stop('gene ',g,' is not found')
df <- data.frame(val=unlist(x),cell=unlist(lapply(x,names)))
df$cluster <- ann[match(df$cell,names(ann))]
df <- na.omit(df)
mv <- max(tapply(df$val,df$cluster,quantile,p=0.8),tapply(df$val,df$cluster,mean))*1.5
p <- ggplot(df,aes(x=cluster,y=val,color=cluster))+geom_boxplot(outlier.shape = NA)+ stat_summary(fun.data=mean_se,geom="pointrange", color="black")+ylab(g)+ggtitle(g)+guides(colour=FALSE)+ theme(axis.text.x = element_text(angle = 90, hjust = 1,vjust=0.5))+coord_cartesian(ylim=c(0,mv));
p
}
eli.genes <- c("CXCR4",'GZMB','GZMA','PDCD1','HAVCR2','TIGIT','LAG3','TCF7','ENTPD1','GNLY','CX3CR1','PRF1','SLAMF6')
pl <- lapply(eli.genes,qplot,con.obj=scon,ann=typef)
cowplot::plot_grid(plotlist=pl,ncol=2)
qplot('TGFB2',scon,ann=typef)
Extended differential expression tests
Extend the getPerCellTypeDE function to perform per-sample Wilcoxon test, bootstrap resampled test, as well as a combined Wilcoxon test.
# given a vector of Z scores from resamplign rounds, returns a conservative lower bound of the Z score using a specified quantile (reproducibility power)
conservative.Z <- function(z, quantile) {
bq <- as.numeric(sort(quantile(z,p=c(quantile,1-quantile))))
if(sign(bq[1])!=sign(bq[2])) return(0) # confidence interval includes 0
return(bq[which.min(abs(bq))])
}
# given a vector of p-values from resampling rounds, returns an upper bound based on the desired quantile
conservative.pval <- function(pval, quantile) {
as.numeric(max(quantile(pval,p=c(quantile,1-quantile))))
}
# cm - expression matrix with rows being cells
# cols - a factor on the rows (must match order)
# return: raw and adjusted Z scores
rowWiseWilcoxonTest <- function(cm, cols, lower.lpv.limit=-100) {
# run wilcoxon test comparing each group with the rest
# calculate rank per-column (per-gene) average rank matrix
xr <- pagoda2:::sparse_matrix_column_ranks(cm);
# calculate rank sums per group
grs <- pagoda2:::colSumByFac(xr,as.integer(cols))[-1,,drop=F]
# calculate number of non-zero entries per group
xr@x <- numeric(length(xr@x))+1
gnzz <- pagoda2:::colSumByFac(xr,as.integer(cols))[-1,,drop=F]
#group.size <- as.numeric(tapply(cols,cols,length));
group.size <- as.numeric(tapply(cols,cols,length))[1:nrow(gnzz)]; group.size[is.na(group.size)]<-0; # trailing empty levels are cut off by colSumByFac
# add contribution of zero entries to the grs
gnz <- (group.size-gnzz)
# rank of a 0 entry for each gene
zero.ranks <- (nrow(xr)-diff(xr@p)+1)/2 # number of total zero entries per gene
ustat <- t((t(gnz)*zero.ranks)) + grs - group.size*(group.size+1)/2
# standardize
n1n2 <- group.size*(nrow(cm)-group.size);
# usigma <- sqrt(n1n2*(nrow(cm)+1)/12) # without tie correction
# correcting for 0 ties, of which there are plenty
usigma <- sqrt(n1n2*(nrow(cm)+1)/12)
usigma <- sqrt((nrow(cm) +1 - (gnz^3 - gnz)/(nrow(cm)*(nrow(cm)-1)))*n1n2/12)
z <- t((ustat - n1n2/2)/usigma); # standardized U value- z score
cz <- matrix(qnorm(pagoda2:::bh.adjust(pnorm(as.numeric(abs(z)), lower.tail = FALSE, log.p = TRUE), log = TRUE), lower.tail = FALSE, log.p = TRUE),ncol=ncol(z))*sign(z)
rownames(z) <- rownames(cz) <- colnames(cm); colnames(z) <- colnames(cz) <- levels(cols)[1:ncol(z)];
return(list(z=z,cz=cz))
}
#' Do differential expression for each cell type in a conos object between the specified subsets of apps
#' @param con.obj conos object
#' @param groups factor specifying cell types
#' @param sample.groups a list of two character vector specifying the app groups to compare
#' @param cooks.cutoff cooksCutoff for DESeq2
#' @param independent.filtering independentFiltering for DESeq2
#' @param n.cores number of cores
#' @param cluster.sep.chr character string of length 1 specifying a delimiter to separate cluster and app names
#' @param return.details return detals
#' @export getPerCellTypeDE
getPerCellTypeDE2 <- function(con.obj, groups=NULL, sample.groups=NULL, cooks.cutoff = FALSE, ref.level = NULL, min.cell.count = 10,
independent.filtering = FALSE, n.cores=1, cluster.sep.chr = '<!!>',return.details=TRUE, n.bootstraps=0, bootstrap.quantile=0.9) {
conos:::validatePerCellTypeParams(con.obj, groups, sample.groups, ref.level, cluster.sep.chr)
## Generate a summary dataset collapsing the cells of the same type in each sample
## and merging everything in one matrix
cml <- conos:::rawMatricesWithCommonGenes(con.obj, sample.groups)
aggr2 <- cml %>% lapply(conos:::collapseCellsByType, groups=groups, min.cell.count=min.cell.count) %>% conos:::rbindDEMatrices(cluster.sep.chr=cluster.sep.chr)
# sample groups factor
sample.gfac <- setNames(rep(names(sample.groups),unlist(lapply(sample.groups,length))),unlist(sample.groups))
# cell sample factor
samf <- setNames(rep(names(cml),unlist(lapply(cml,nrow))),unlist(lapply(cml,rownames)))
# cell groups factor
cell.gfac <- setNames(sample.gfac[samf],names(samf))
## For every cell type get differential expression results
de.res <- conos:::papply(conos:::sn(levels(groups)), function(l) {
tryCatch({
## Get count matrix
cm <- aggr2[,strpart(colnames(aggr2),cluster.sep.chr,2,fixed=TRUE) == l]
## Generate metadata
meta <- data.frame(
sample.id= colnames(cm),
group= as.factor(unlist(lapply(colnames(cm), function(y) {
y <- strpart(y,cluster.sep.chr,1,fixed=TRUE)
names(sample.groups)[unlist(lapply(sample.groups,function(x) any(x %in% y)))]
})))
)
if (!ref.level %in% levels(meta$group))
stop('The reference level is absent in this comparison')
meta$group <- relevel(meta$group, ref=ref.level)
if (length(unique(as.character(meta$group))) < 2)
stop('The cluster is not present in both conditions')
dds1 <- DESeq2::DESeqDataSetFromMatrix(cm,meta,design=~group)
dds1 <- DESeq2::DESeq(dds1)
res1 <- DESeq2::results(dds1, cooksCutoff = cooks.cutoff, independentFiltering = independent.filtering)
res1 <- as.data.frame(res1)
# filter cml to leave only the cells of that type and drop the samples with insufficient cells
cmlf <- lapply(cml,function(cm) {
vc <- intersect(rownames(cm), names(groups)[groups==l])
if(length(vc)<min.cell.count) return(NULL)
cm[vc,,drop=F]
})
cmlf <- cmlf[!unlist(lapply(cmlf,is.null))]
# dataset-wise wilcoxon test
x <- rowWiseWilcoxonTest(as(t(cm),'dgCMatrix'), as.factor(meta$group))
res1$wZ <- -x$z[,ref.level]
res1$wZadj <- -x$cz[,ref.level]
#res1$wP <- pmin(pnorm(x$z[,ref.level]),pnorm(x$z[,ref.level],lower.tail=F))
#res1$wPadj <- pmin(pnorm(x$cz[,ref.level]),pnorm(x$cz[,ref.level],lower.tail=F))
# resampled dataset-wise wilcoxon tests
if(n.bootstraps>0) {
xx <- lapply(1:n.bootstraps,function(i) {
cm <- do.call(rbind,lapply(cmlf,function(x) colSums(x[sample(rownames(x),replace = T),])))
rowWiseWilcoxonTest(as(cm,'dgCMatrix'), as.factor(sample.gfac[rownames(cm)]))
})
xx <- do.call(rbind,lapply(xx,function(x) x$z[,ref.level]))
res1$bwZ <- -apply(xx,2,conservative.Z,quantile=bootstrap.quantile)
res1$bwZadj <- qnorm(pagoda2:::bh.adjust(pnorm(as.numeric(abs(res1$bwZ)), lower.tail = FALSE, log.p = TRUE), log = TRUE), lower.tail = FALSE, log.p = TRUE)*sign(res1$bwZ)
}
# cell-wise wilcoxon test
cm2 <- do.call(rbind,cmlf)
x <- rowWiseWilcoxonTest(cm2, as.factor(cell.gfac[rownames(cm2)]))
# res1$cwP <- pmin(pnorm(x$z[,ref.level]),pnorm(x$z[,ref.level],lower.tail=F))
# res1$cwPadj <- pmin(pnorm(x$cz[,ref.level]),pnorm(x$cz[,ref.level],lower.tail=F))
res1$cwZ <- -x$z[,ref.level]
res1$cwZadj <- -x$cz[,ref.level]
res1 <- res1[order(res1$padj,decreasing = FALSE),]
gc()
##
if(return.details) {
list(res=res1, cm=cm, sample.groups=sample.groups)
} else {
res1
}
}, error=function(err) {warning("Error for level ", l, ": ", err$message); return(NA)})
}, n.cores=n.cores)
de.res
}
samplegroups <- list(
Benign = grep("-Whole",names(scon$samples),val=T),
Tumor = grep("-Tumor",names(scon$samples),val=T)
)
de.info2 <- getPerCellTypeDE2(scon, groups=nfac, sample.groups = samplegroups, ref.level='Benign', n.cores=30,n.bootstraps=100)
#saveRDS(de.info2,file='de.TB.rds')
Writing out JSON files with the updated test info:
source("~pkharchenko/m/pavan/DLI/conp2.r")
dir.create('json',showWarnings = F)
# need to get rid of spaces in the names
names(de.info2) <- gsub(" ","_",names(de.info2))
saveDEasJSON2(de.info2,'json/')
write out link table: note that you’ll need to change the location variable to specify where the folder with all the json files and deview.3.html (that has to be copied there manually) will be located on the server:
# path - local path where to save the TOC file
# location - web server path where the json files and the toc file will reside
write.toc.file <- function(de.info,path='json',fname='toc.html',location='http://pklab.med.harvard.edu/peterk/scadden/bmmet/supp/json') {
toc.file <- paste(path,fname,sep='/')
s <- paste(c(list('<html><head><style>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
</style></head><body><table>'),
suppressWarnings(lapply(names(de.info),function(n) { if(!is.na(de.info[[n]])) { ref <- paste0('<a href="',location,'/deview.3.html?d=',n,'.json">',n,'</a>') } else {ref <- 'NA'}; paste0('<tr><td>',ref,'</td></tr>') })),
list('</table></body></html>')),collapse='\n')
write(s,file=toc.file)
}
write.toc.file(de.info2,path='json',location='http://pklab.med.harvard.edu/peterk/scadden/bmmet/supp/json')
Calculate all contrasts:
dir.create('json.TB',showWarnings = F)
saveDEasJSON2(de.TB,'json.TB/')
dir.create('json.IB',showWarnings = F)
saveDEasJSON2(de.IB,'json.IB/')
write.toc.file(de.TB,path='json.TB',location='http://pklab.med.harvard.edu/peterk/scadden/bmmet/supp/json.TB')
write.toc.file(de.IB,path='json.IB',location='http://pklab.med.harvard.edu/peterk/scadden/bmmet/supp/json.IB')
LS0tCnRpdGxlOiAiQk0gcmV2aXNpb24gcGxvdHMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyfQpsaWJyYXJ5KHBhZ29kYTIpCmxpYnJhcnkoY29ub3MpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KHBhcmFsbGVsKQpsaWJyYXJ5KGNvd3Bsb3QpCmBgYAoKCgpMb2FkIG9yaWdpbmFsIGNvbm9zIG9iamVjdCBhbmQgYW5ub3RhdGlvbnMKCmBgYHtyfQpzY29uIDwtIENvbm9zJG5ldyhyZWFkUkRTKCJ+cGtoYXJjaGVua28vbS9zY2FkZGVuL2JtbWV0L2phbjIwMTkvc2Nvbi5yZHMiKSkKbG9hZCgifnBraGFyY2hlbmtvL20vc2NhZGRlbi9ibW1ldC9qYW4yMDE5L2Fubm90YXRpb25zLlJEYXRhIikKdHlwZWZjIDwtIHJlYWRSRFMoIi9kMC1tZW5kZWwvaG9tZS9tZWlzbC9Xb3JrcGxhY2UvQk1NRS9GaWd1cmVzL2RhdGEvY2VsbC5hbm8ubWVyZ2VkLnJkcyIpCnR5cGVmYy5wYWwgPC0gcmVhZFJEUygiL2QwLW1lbmRlbC9ob21lL21laXNsL1dvcmtwbGFjZS9CTU1FL0ZpZ3VyZXMvZGF0YS9jZWxsVHlwZS5jb2xvci5yZHMiKQpgYGAKCgpMb29rIGF0IHRoZSBjb2Fyc2UgYW5ub3RhdGlvbjoKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KYWxwaGEgPC0gMC4xOyBzaXplIDwtIDAuMjsgCnAxIDwtIHNjb24kcGxvdEdyYXBoKGdyb3Vwcz10eXBlZmMscGFsZXR0ZT10eXBlZmMucGFsLGFscGhhPWFscGhhLHNpemU9c2l6ZSxtYXJrLmdyb3Vwcz1ULHBsb3QubmE9RikKcDEKYGBgCgoKCkxvb2sgYXQgdGhlIGRldGFpbGVkIGFubm90YXRpb246CgpgYGB7ciBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQphbHBoYSA8LSAwLjE7IHNpemUgPC0gMC4yOyAKcDIgPC0gc2NvbiRwbG90R3JhcGgoZ3JvdXBzPXR5cGVmLGFscGhhPWFscGhhLHNpemU9c2l6ZSxtYXJrLmdyb3Vwcz1ULHBsb3QubmE9RikKcDIKYGBgCgoKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KYWxwaGEgPC0gMC4xOyBzaXplIDwtIDAuMjsgCnNjb24kcGxvdEdyYXBoKGdlbmU9J01ETTQnLGFscGhhPTAuNCxzaXplPXNpemUsbWFyay5ncm91cHM9VCxwbG90Lm5hPUYpCmBgYAoKIyMjIERvdWJsZXRzCgpgYGB7cn0Kc291cmNlKCJ+L20vcGF2YW4vRExJL2NvbnAyLnIiKQpkbCA8LSBsYXBwbHkoc2NvbiRzYW1wbGVzLGZ1bmN0aW9uKHgpIHQoeCRtaXNjJHJhd0NvdW50cykpOwoKZHMgPC0gbWNsYXBwbHkoZGwsZ2V0LnNjcnVibGV0LnNjb3JlcyxtYy5jb3Jlcz0zMCkKZG91YmxldC5mIDwtIHNldE5hbWVzKHVubGlzdChkcyksdW5saXN0KGxhcHBseShkcyxuYW1lcykpKQpgYGAKCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CmFscGhhIDwtIDAuMTsgc2l6ZSA8LSAwLjI7IApzY29uJHBsb3RHcmFwaChncm91cHM9YXMuZmFjdG9yKGRvdWJsZXQuZltuYW1lcyhkb3VibGV0LmYpICVpbiUgbmFtZXModHlwZWYpXT4wLjI1KSxwYWxldHRlPWMoIlRSVUUiPSdyZWQnLCdGQUxTRSc9J2dyYXk3MCcpLGFscGhhPWFscGhhLHNpemU9c2l6ZSxtYXJrLmdyb3Vwcz1GLHBsb3QubmE9RikKYGBgCgpgYGB7ciBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD01fQphbHBoYSA8LSAwLjE7IHNpemUgPC0gMC4yOyAKcDEgPC0gc2NvbiRwbG90R3JhcGgoZ3JvdXBzPXR5cGVmY1tkb3VibGV0LmZbbmFtZXModHlwZWZjKV08PTAuMjVdLHBhbGV0dGU9dHlwZWZjLnBhbCxhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9VCxwbG90Lm5hPUYpCnAxCmBgYAoKCiMjIyBNYXJrZXIgZ2VuZXMKCk1hcmtlciBnZW5lcwpgYGB7cn0KbmZhYyA8LSBhcy5mYWN0b3IodHlwZWZjW2RvdWJsZXQuZltuYW1lcyh0eXBlZmMpXTw9MC4yNV0pCmBgYAoKYGBge3J9CmFubm90LmRlIDwtIHNjb24kZ2V0RGlmZmVyZW50aWFsR2VuZXMoZ3JvdXBzPW5mYWMsbi5jb3Jlcz0zMCxhcHBlbmQuYXVjPVRSVUUsei50aHJlc2hvbGQ9MCx1cHJlZ3VsYXRlZC5vbmx5PVQpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD0xMixmaWcuaGVpZ2h0PTEyfQojc291cmNlKCJ+L20vcDIvY29ub3MvUi9wbG90LlIiKQpwcCA8LSBwbG90REVoZWF0bWFwKHNjb24sbmZhYyxhbm5vdC5kZSxuLmdlbmVzLnBlci5jbHVzdGVyID0gMjAgLHNob3cuZ2VuZS5jbHVzdGVycz1ULGNvbHVtbi5tZXRhZGF0YT1saXN0KHNhbXBsZXM9c2NvbiRnZXREYXRhc2V0UGVyQ2VsbCgpKSxvcmRlci5jbHVzdGVycyA9IFQsIGNvbHVtbi5tZXRhZGF0YS5jb2xvcnMgPSBsaXN0KGNsdXN0ZXJzPXR5cGVmYy5wYWwpLHVzZV9yYXN0ZXIgPSBULHJhc3Rlcl9kZXZpY2UgPSAiQ2Fpcm9QTkciKQpwZGYoZmlsZT0nYW5ub3QuaGVhdG1hcC5wZGYnLHdpZHRoPTEwLGhlaWdodD0zMCk7IHByaW50KHBwKTsgZGV2Lm9mZigpOwpgYGAKCkEgc21hbGwgdmVyc2lvbiBvZiB0aGUgaGV0bWFwLCBmb3IgdGhlIG1haW4gZmlndXJlCmBgYHtyIGZpZy53aWR0aD02LGZpZy5oZWlnaHQ9OH0Kc291cmNlKCJ+L20vcDIvY29ub3MvUi9wbG90LlIiKQpnZW5lcyA8LSBjKCdNUzRBMScsJ0NENzlBJywnQ0Q3OUInLCdNWkIxJywnVlBSRUIzJywnU0VDMTFDJywnSUdMTDUnLCdHTkxZJywnR1pNQicsJ0xJTFJBNCcsJ0FaVTEnLCdNUE8nLCdGQ04xJywnSUVSMycsJ0M1QVIxJywnSUdKJywnU09YNCcsJ1NUTU4xJywnTVlMOScsJ0hCRCcsJ0daTUsnLCdBUicsJ0tMSzInKQpwcCA8LSBwbG90REVoZWF0bWFwKHNjb24sbmZhYyxhbm5vdC5kZSxuLmdlbmVzLnBlci5jbHVzdGVyID0gMjAgLHNob3cuZ2VuZS5jbHVzdGVycz1ULCBjb2x1bW4ubWV0YWRhdGEuY29sb3JzID0gbGlzdChjbHVzdGVycz10eXBlZmMucGFsKSwgb3JkZXIuY2x1c3RlcnMgPSBULCBhZGRpdGlvbmFsLmdlbmVzID0gZ2VuZXMsIGxhYmVsZWQuZ2VuZS5zdWJzZXQgPSBnZW5lcywgbWluLmF1YyA9IDAuNix1c2VfcmFzdGVyID0gVCxyYXN0ZXJfZGV2aWNlID0gIkNhaXJvUE5HIikKcHAKYGBgCgoKCgoKYGBge3IgZmlnLndpZHRoPTUsIGZpZy5oZWlnaHQ9NX0KYWxwaGEgPC0gMC41OyBzaXplIDwtIDAuMTsgCnNjb24kcGxvdEdyYXBoKGdlbmU9J0tMSzInLGFscGhhPWFscGhhLHNpemU9c2l6ZSxtYXJrLmdyb3Vwcz1GLHBsb3QubmE9RikKYGBgCgojIyMgU2FtcGxlIHBhbmVsOgpgYGB7ciBmaWcuaGVpZ2h0PTI0LCBmaWcud2lkdGg9MTJ9CnBwIDwtIHNjb24kcGxvdFBhbmVsKGdyb3Vwcz1uZmFjLHBsb3QubmE9RixhbHBoYT0wLjMsc2l6ZT0wLjUsdXNlLmNvbW1vbi5lbWJlZGRpbmcgPSBGLCBuY29sPTQsIG1hcmsuZ3JvdXBzPVQscGFsZXR0ZT10eXBlZmMucGFsLHJhc3Rlcj1ULCByYXN0ZXIud2lkdGg9MyxyYXN0ZXIuaGVpZ2h0PTMsdGl0bGUuc2l6ZT00LGZvbnQuc2l6ZT1jKDMsNCkpCiNwZGYoJ3BhbmVsLnBkZicsaGVpZ2h0PTgsd2lkdGg9Mik7IHByaW50KHBwKTsgZGV2Lm9mZigpOwpwcApgYGAKCgpKdXN0IHRoZSBUdW1vciBmcmFjdGlvbnMKYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpwbCA8LSBsYXBwbHkoZ3JlcCgiVHVtb3IiLG5hbWVzKHNjb24kc2FtcGxlcyksdmFsPVQpLGZ1bmN0aW9uKG5hbSkgewogIHNjY29yZTo6ZW1iZWRkaW5nUGxvdChuY29uJHNhbXBsZXNbW25hbV1dJGVtYmVkZGluZ3MkUENBJHRTTkUsZ3JvdXBzPW5mYWMscGFsZXR0ZT10eXBlZmMucGFsLHBsb3QubmE9RixyYXN0ZXI9VCxyYXN0ZXIuaGVpZ2h0PTMscmFzdGVyLndpZHRoPTMsZm9udC5zaXplPWMoMyw0KSkgKyBnZ3RpdGxlKG5hbSkgK3RoZW1lX2J3KCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsgdGhlbWUoYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLHBhbmVsLmdyaWQubWFqb3IgPSBlbGVtZW50X2JsYW5rKCksIHBhbmVsLmdyaWQubWlub3I9ZWxlbWVudF9ibGFuaygpKQp9KQpwbG90X2dyaWQocGxvdGxpc3Q9cGwsbnJvdz0zKQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0KcGwgPC0gbGFwcGx5KGdyZXAoIlR1bW9yIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKSxmdW5jdGlvbihuYW0pIHsKICBzY2NvcmU6OmVtYmVkZGluZ1Bsb3QobmNvbiRzYW1wbGVzW1tuYW1dXSRlbWJlZGRpbmdzJFBDQSR0U05FLGNvbG9ycz1jb25vczo6OmdldEdlbmVFeHByZXNzaW9uKG5jb24kc2FtcGxlc1tbbmFtXV0sJ0tMSzInKSxwbG90Lm5hPUYscmFzdGVyPVQscmFzdGVyLmhlaWdodD0zLHJhc3Rlci53aWR0aD0zLGZvbnQuc2l6ZT1jKDMsNCkpICsgZ2d0aXRsZShuYW0pICt0aGVtZV9idygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArIHRoZW1lKGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSxwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSkKfSkKcGxvdF9ncmlkKHBsb3RsaXN0PXBsLG5yb3c9MykKYGBgCgojIyBFeHByZXNzaW9uIGRpZmZlcmVuY2UgbWFnbml0dWRlIGFuYWx5c2lzCkFuIGV4dGVuZGVkIHZlcnNpb24sIG5vbi1wYWlyZWQgY29tcGFyaXNvbnMuIAoKYGBge3J9CnNvdXJjZSgifnBraGFyY2hlbmtvL20vcDIvY29tcC9jYWNvYS9SL2V4cHJlc3Npb25fc2hpZnRzLlIiKQoKc2FtcGxlZ3JvdXBzIDwtIGxpc3QoCiAgQmVuaWduID0gZ3JlcCgiLVdob2xlIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKSwKICBUdW1vciA9IGdyZXAoIi1UdW1vciIsbmFtZXMoc2NvbiRzYW1wbGVzKSx2YWw9VCkKKQpzZ2YgPC0gc2V0TmFtZXMocmVwKG5hbWVzKHNhbXBsZWdyb3VwcyksdW5saXN0KGxhcHBseShzYW1wbGVncm91cHMsbGVuZ3RoKSkpLHVubGlzdChzYW1wbGVncm91cHMpKQoKeFRCIDwtIGVzdGltYXRlRXhwcmVzc2lvblNoaWZ0TWFnbml0dWRlcyhsYXBwbHkoc2NvbiRzYW1wbGVzLGNvbm9zOjo6Z2V0UmF3Q291bnRNYXRyaXgpLHNhbXBsZS5ncm91cHM9c2dmLGNlbGwuZ3JvdXBzPW5mYWMsZGlzdD0nSlMnLG4uY2VsbHM9NTAwLG4uc3Vic2FtcGxlcz0xMDAsbWluLmNlbGxzPTEwLG4uY29yZXM9MzApIAp4VEJjIDwtIGVzdGltYXRlRXhwcmVzc2lvblNoaWZ0TWFnbml0dWRlcyhsYXBwbHkoc2NvbiRzYW1wbGVzLGNvbm9zOjo6Z2V0UmF3Q291bnRNYXRyaXgpLHNhbXBsZS5ncm91cHM9c2dmLGNlbGwuZ3JvdXBzPW5mYWMsZGlzdD0nY29yJyxuLmNlbGxzPTUwMCxuLnN1YnNhbXBsZXM9MTAwLG1pbi5jZWxscz0xMCxuLmNvcmVzPTMwKSAKCgpzYW1wbGVncm91cHMgPC0gbGlzdCgKICBCZW5pZ24gPSBncmVwKCItV2hvbGUiLG5hbWVzKHNjb24kc2FtcGxlcyksdmFsPVQpLAogIFR1bW9yID0gZ3JlcCgiLUludm9sdmVkIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKQopCnNnZiA8LSBzZXROYW1lcyhyZXAobmFtZXMoc2FtcGxlZ3JvdXBzKSx1bmxpc3QobGFwcGx5KHNhbXBsZWdyb3VwcyxsZW5ndGgpKSksdW5saXN0KHNhbXBsZWdyb3VwcykpCgp4SUIgPC0gZXN0aW1hdGVFeHByZXNzaW9uU2hpZnRNYWduaXR1ZGVzKGxhcHBseShzY29uJHNhbXBsZXMsY29ub3M6OjpnZXRSYXdDb3VudE1hdHJpeCksc2FtcGxlLmdyb3Vwcz1zZ2YsY2VsbC5ncm91cHM9bmZhYyxkaXN0PSdKUycsbi5jZWxscz01MDAsbi5zdWJzYW1wbGVzPTEwMCxtaW4uY2VsbHM9MTAsbi5jb3Jlcz0zMCkgCnhJQmMgPC0gZXN0aW1hdGVFeHByZXNzaW9uU2hpZnRNYWduaXR1ZGVzKGxhcHBseShzY29uJHNhbXBsZXMsY29ub3M6OjpnZXRSYXdDb3VudE1hdHJpeCksc2FtcGxlLmdyb3Vwcz1zZ2YsY2VsbC5ncm91cHM9bmZhYyxkaXN0PSdjb3InLG4uY2VsbHM9NTAwLG4uc3Vic2FtcGxlcz0xMDAsbWluLmNlbGxzPTEwLG4uY29yZXM9MzApIAoKc2FtcGxlZ3JvdXBzIDwtIGxpc3QoCiAgQmVuaWduID0gZ3JlcCgiLU5vbmludm9sdmVkIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKSwKICBUdW1vciA9IGdyZXAoIi1JbnZvbHZlZCIsbmFtZXMoc2NvbiRzYW1wbGVzKSx2YWw9VCkKKQpzZ2YgPC0gc2V0TmFtZXMocmVwKG5hbWVzKHNhbXBsZWdyb3VwcyksdW5saXN0KGxhcHBseShzYW1wbGVncm91cHMsbGVuZ3RoKSkpLHVubGlzdChzYW1wbGVncm91cHMpKQoKeElEIDwtIGVzdGltYXRlRXhwcmVzc2lvblNoaWZ0TWFnbml0dWRlcyhsYXBwbHkoc2NvbiRzYW1wbGVzLGNvbm9zOjo6Z2V0UmF3Q291bnRNYXRyaXgpLHNhbXBsZS5ncm91cHM9c2dmLGNlbGwuZ3JvdXBzPW5mYWMsZGlzdD0nSlMnLG4uY2VsbHM9NTAwLG4uc3Vic2FtcGxlcz0xMDAsbWluLmNlbGxzPTEwLG4uY29yZXM9MzApIAp4SURjIDwtIGVzdGltYXRlRXhwcmVzc2lvblNoaWZ0TWFnbml0dWRlcyhsYXBwbHkoc2NvbiRzYW1wbGVzLGNvbm9zOjo6Z2V0UmF3Q291bnRNYXRyaXgpLHNhbXBsZS5ncm91cHM9c2dmLGNlbGwuZ3JvdXBzPW5mYWMsZGlzdD0nY29yJyxuLmNlbGxzPTUwMCxuLnN1YnNhbXBsZXM9MTAwLG1pbi5jZWxscz0xMCxuLmNvcmVzPTMwKSAKCgpzYW1wbGVncm91cHMgPC0gbGlzdCgKICBJbnZvbHZlZCA9IGdyZXAoIi1JbnZvbHZlZCIsbmFtZXMoc2NvbiRzYW1wbGVzKSx2YWw9VCksCiAgVHVtb3IgPSBncmVwKCItVHVtb3IiLG5hbWVzKHNjb24kc2FtcGxlcyksdmFsPVQpCikKc2dmIDwtIHNldE5hbWVzKHJlcChuYW1lcyhzYW1wbGVncm91cHMpLHVubGlzdChsYXBwbHkoc2FtcGxlZ3JvdXBzLGxlbmd0aCkpKSx1bmxpc3Qoc2FtcGxlZ3JvdXBzKSkKCnhJVCA8LSBlc3RpbWF0ZUV4cHJlc3Npb25TaGlmdE1hZ25pdHVkZXMobGFwcGx5KHNjb24kc2FtcGxlcyxjb25vczo6OmdldFJhd0NvdW50TWF0cml4KSxzYW1wbGUuZ3JvdXBzPXNnZixjZWxsLmdyb3Vwcz1uZmFjLGRpc3Q9J0pTJyxuLmNlbGxzPTUwMCxuLnN1YnNhbXBsZXM9MTAwLG1pbi5jZWxscz0xMCxuLmNvcmVzPTMwKSAKeElUYyA8LSBlc3RpbWF0ZUV4cHJlc3Npb25TaGlmdE1hZ25pdHVkZXMobGFwcGx5KHNjb24kc2FtcGxlcyxjb25vczo6OmdldFJhd0NvdW50TWF0cml4KSxzYW1wbGUuZ3JvdXBzPXNnZixjZWxsLmdyb3Vwcz1uZmFjLGRpc3Q9J2Nvcicsbi5jZWxscz01MDAsbi5zdWJzYW1wbGVzPTEwMCxtaW4uY2VsbHM9MTAsbi5jb3Jlcz0zMCkgCgoKCnNhbXBsZWdyb3VwcyA8LSBsaXN0KAogIERpc3RhbCA9IGdyZXAoIi1Ob25pbnZvbHZlZCIsbmFtZXMoc2NvbiRzYW1wbGVzKSx2YWw9VCksCiAgVHVtb3IgPSBncmVwKCItVHVtb3IiLG5hbWVzKHNjb24kc2FtcGxlcyksdmFsPVQpCikKc2dmIDwtIHNldE5hbWVzKHJlcChuYW1lcyhzYW1wbGVncm91cHMpLHVubGlzdChsYXBwbHkoc2FtcGxlZ3JvdXBzLGxlbmd0aCkpKSx1bmxpc3Qoc2FtcGxlZ3JvdXBzKSkKCnhURCA8LSBlc3RpbWF0ZUV4cHJlc3Npb25TaGlmdE1hZ25pdHVkZXMobGFwcGx5KHNjb24kc2FtcGxlcyxjb25vczo6OmdldFJhd0NvdW50TWF0cml4KSxzYW1wbGUuZ3JvdXBzPXNnZixjZWxsLmdyb3Vwcz1uZmFjLGRpc3Q9J0pTJyxuLmNlbGxzPTUwMCxuLnN1YnNhbXBsZXM9MTAwLG1pbi5jZWxscz0xMCxuLmNvcmVzPTMwKSAKeFREYyA8LSBlc3RpbWF0ZUV4cHJlc3Npb25TaGlmdE1hZ25pdHVkZXMobGFwcGx5KHNjb24kc2FtcGxlcyxjb25vczo6OmdldFJhd0NvdW50TWF0cml4KSxzYW1wbGUuZ3JvdXBzPXNnZixjZWxsLmdyb3Vwcz1uZmFjLGRpc3Q9J2Nvcicsbi5jZWxscz01MDAsbi5zdWJzYW1wbGVzPTEwMCxtaW4uY2VsbHM9MTAsbi5jb3Jlcz0zMCkgCgoKYGBgCgpKUy1iYXNlZCBkaXN0YW5jZXMKYGBge3IgZmlnLmhlaWdodD04LGZpZy53aWR0aD0xMn0Kcmxpc3QgPC0gbGlzdCgiVHVtb3IgdnMuIEJlbmlnbiI9eFRCLCJJbnZvbHZlZCB2cy4gQmVuaWduIj14SUIsIlR1bW9yIHZzLiBJbnZvbHZlZCI9eElULCJUdW1vciB2cy4gRGlzdGFsIj14VEQsIkludm9sdmVkIHZzLiBEaXN0YWwiPXhJRCkKcGwgPC0gbGFwcGx5KG5hbWVzKHJsaXN0KSxmdW5jdGlvbihuYW0pIHsgCiAgZ2dwbG90KHJsaXN0W1tuYW1dXSRkZixhZXMoeD1hcy5mYWN0b3IoVHlwZSkseT12YWx1ZSkpICsgCiAgICBnZW9tX2JveHBsb3Qobm90Y2g9VCxvdXRsaWVyLnNoYXBlPU5BKSArIAogICAgZ2VvbV9qaXR0ZXIocG9zaXRpb249cG9zaXRpb25faml0dGVyKDAuMSksIGFlcyhjb2xvcj1wYXRpZW50KSxzaG93LmxlZ2VuZD1GQUxTRSxhbHBoYT0wLjEpICsgCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxLGxpbmV0eXBlPTIsY29sb3I9J2dyYXk4MCcpICsKICAgIHlsaW0oMCxtaW4oNSxtYXgocmxpc3RbW25hbV1dJGRmJHZhbHVlKSkpKwogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0PTEpLCBheGlzLnRleHQueT1lbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTAuNSkpICsKICAgIGdndGl0bGUobmFtKSsKICAgIGxhYnMoeD0iIiwgeT0ibm9ybWFsaXplZCBkaXN0YW5jZSIpCn0pCnBsb3RfZ3JpZChwbG90bGlzdD1wbCxucm93PTIpCmBgYApDb3JyZWxhdGlvbi1iYXNlZCBmaWd1cmUKYGBge3IgZmlnLmhlaWdodD04LGZpZy53aWR0aD03fQpybGlzdCA8LSBsaXN0KCJUdW1vciB2cy4gQmVuaWduIj14VEJjLCJJbnZvbHZlZCB2cy4gQmVuaWduIj14SUJjLCJUdW1vciB2cy4gRGlzdGFsIj14VERjLCJJbnZvbHZlZCB2cy4gRGlzdGFsIj14SURjKQpwbCA8LSBsYXBwbHkobmFtZXMocmxpc3QpLGZ1bmN0aW9uKG5hbSkgeyAKICBnZ3Bsb3Qocmxpc3RbW25hbV1dJGRmLGFlcyh4PWFzLmZhY3RvcihUeXBlKSx5PXZhbHVlKSkgKyAKICAgIGdlb21fYm94cGxvdChub3RjaD1ULG91dGxpZXIuc2hhcGU9TkEpICsgCiAgICBnZW9tX2ppdHRlcihwb3NpdGlvbj1wb3NpdGlvbl9qaXR0ZXIoMC4xKSwgYWVzKGNvbG9yPXBhdGllbnQpLHNob3cubGVnZW5kPUZBTFNFLGFscGhhPTAuMSkgKyAKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEsbGluZXR5cGU9Mixjb2xvcj0nZ3JheTgwJykgKwogICAgI3lsaW0oMCxtaW4oNSxtYXgocmxpc3RbW25hbV1dJGRmJHZhbHVlKSkpKwogICAgeWxpbSgwLDUpKwogICAgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0PTEpLCBheGlzLnRleHQueT1lbGVtZW50X3RleHQoYW5nbGU9OTAsIGhqdXN0PTAuNSkpICsKICAgIGdndGl0bGUobmFtKSsKICAgIGxhYnMoeD0iIiwgeT0ic3RhbmRhcmRpemVkIGRpc3RhbmNlIikKfSkKcHAgPC0gcGxvdF9ncmlkKHBsb3RsaXN0PXBsLG5yb3c9MikKcGRmKGZpbGU9J2RpZmZlcmVuY2UubWFnbml0dWRlcy5wZGYnLHdpZHRoPTcsaGVpZ2h0PTgpOyBwcmludChwcCk7IGRldi5vZmYoKTsKcHAKYGBgCgoKCgoKCiMjIFJlYWxpZ25tZW50IG9mIHRoZSBUdW1vciBmcmFjdGlvbiB3aXRob3V0IHRyYW5zbGF0aW9uL21pdG9jaG9uZHJpYWwgZ2VuZXMKCmBgYHtyfQpwMmwgPC0gbGFwcGx5KGxhcHBseShkbCxmdW5jdGlvbih4KSB4WywhZG91YmxldC5mW2NvbG5hbWVzKHgpXT4wLjI1XSksIGJhc2ljUDJwcm9jLCBuLmNvcmVzPTMwLCBtaW4uY2VsbHMucGVyLmdlbmU9MCxtaW4udHJhbnNjcmlwdHMucGVyLmNlbGw9MWUzLCBuLm9kZ2VuZXM9MmUzLCBnZXQubGFyZ2V2aXM9RkFMU0UsIG1ha2UuZ2VuZWtubj1GQUxTRSkKYGBgCgpSZW1vdmUgdHJhbnNsYXRpb24gYW5kIHJlc3BpcmF0b3J5IGNoYWluIGdlbmVzIHRoYXQgYXJlIGJyaW5naW5nIHR1bW9yIGNlbGxzIGNsb3NlIHRvIHByb2dlbnRpb3JzCmBgYHtyfQpsaWJyYXJ5KGJpb21hUnQpCmVuc2VtYmwgPSB1c2VNYXJ0KCJlbnNlbWJsIixkYXRhc2V0PSJoc2FwaWVuc19nZW5lX2Vuc2VtYmwiKQojZ2VuZS5kYXRhIDwtIGdldEJNKGF0dHJpYnV0ZXM9YygnaGduY19zeW1ib2wnKSxmaWx0ZXJzID0gJ2dvJywgdmFsdWVzID0gIkdPOjAwMDAyODAiLCBtYXJ0ID0gZW5zZW1ibCkKCiNnZXRCTShhdHRyaWJ1dGVzID0gYygnZW50cmV6Z2VuZV9pZCcsJ2hnbmNfc3ltYm9sJyksIGZpbHRlcnMgPSAnZ28nLCB2YWx1ZXMgPSAnR086MDAwMDI3OCcsIG1hcnQgPSBlbnNlbWJsKQojZ2V0Qk0oYXR0cmlidXRlcyA9IGMoJ2VudHJlemdlbmVfaWQnLCdoZ25jX3N5bWJvbCcpLCBmaWx0ZXJzID0gJ2dvJywgdmFsdWVzID0gJ0dPOjAwMDYyNjAnLCBtYXJ0ID0gZW5zZW1ibCkKdHIuZ2VuZXMgPC0gZ2V0Qk0oYXR0cmlidXRlcyA9IGMoJ2VudHJlemdlbmVfaWQnLCdoZ25jX3N5bWJvbCcpLCBmaWx0ZXJzID0gJ2dvJywgdmFsdWVzID0gYygnR086MDAwNjQxMicsJ0dPOjAwMDY0MTQnLCdHTzowMDIyOTA0JywnR086MDA1NTExNCcsJ0dPOjAwNzAxMjUnLCdHTzowMDA2ODM5JyksIG1hcnQgPSBlbnNlbWJsKQp0ci5nZW5lcyA8LSB0ci5nZW5lcyRoZ25jX3N5bWJvbApzYXZlUkRTKHRyLmdlbmVzLGZpbGU9J3RyLmdlbmVzLnJkcycpCmBgYAoKYGBge3J9CnRyLmdlbmVzIDwtIHJlYWRSRFMoInRyLmdlbmVzLnJkcyIpCnAybCA8LSBsYXBwbHkobGFwcGx5KGRsLGZ1bmN0aW9uKHgpIHhbIXJvd25hbWVzKHgpICVpbiUgdHIuZ2VuZXMsIWRvdWJsZXQuZltjb2xuYW1lcyh4KV0+MC4yNV0pLCBiYXNpY1AycHJvYywgbi5jb3Jlcz0zMCwgbWluLmNlbGxzLnBlci5nZW5lPTAsbWluLnRyYW5zY3JpcHRzLnBlci5jZWxsPTFlMywgbi5vZGdlbmVzPTJlMywgZ2V0LmxhcmdldmlzPUZBTFNFLCBtYWtlLmdlbmVrbm49RkFMU0UpCmBgYAoKCgpDb25vcyBpbnRlZ3JhdGlvbiBvZiBvbmx5IHR1bW9yIGZyYWN0aW9uczoKYGBge3J9Cm5jb24gPC0gQ29ub3MkbmV3KHAybFtncmVwbCgiVHVtb3IiLG5hbWVzKHAybCkpXSxuLmNvcmVzPTMwKQojbmNvbiRidWlsZEdyYXBoKGs9MTUsIGsuc2VsZj01LCBzcGFjZT0nQ1BDQScsIG5jb21wcz0zMCwgbi5vZGdlbmVzPTIwMDAsIHZlcmJvc2U9VFJVRSkKI25jb24kYnVpbGRHcmFwaChrPTE1LCBrLnNlbGY9NSwgc3BhY2U9J0NDQScsIG5jb21wcz0zMCwgbi5vZGdlbmVzPTIwMDAsIHZlcmJvc2U9VFJVRSkKbmNvbiRidWlsZEdyYXBoKGs9MTUsIGsuc2VsZj01LCBzcGFjZT0nUENBJywgbmNvbXBzPTMwLCBuLm9kZ2VuZXM9MjAwMCwgdmVyYm9zZT1UUlVFKQpuY29uJGVtYmVkR3JhcGgobWV0aG9kPSdVTUFQJyk7CiMgc3RvcmUgZGlmZmVyZW50IGVtYmVkZGluZ3MKbmNvbiRtaXNjJGVtYmVkZGluZ3MgPC0gbGlzdCgpCm5jb24kbWlzYyRlbWJlZGRpbmdzJHVtYXAgPC0gbmNvbiRlbWJlZGRpbmcKbmNvbiRlbWJlZEdyYXBoKHNnZF9iYXRjaGVzPTJlOCxhcGhhPTEuNSk7Cm5jb24kbWlzYyRlbWJlZGRpbmdzJGx2IDwtIG5jb24kZW1iZWRkaW5nCgpuY29uJGZpbmRDb21tdW5pdGllcyhtZXRob2Q9bGVpZGVuLmNvbW11bml0eSxyZXNvbHV0aW9uPTEpCmBgYAoKVGFrZSBhIGxvb2sKYGBge3IgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTEwfQphbHBoYSA8LSAwLjE7IHNpemUgPC0gMC41OyAKbmNvbiRlbWJlZGRpbmcgPC0gbmNvbiRtaXNjJGVtYmVkZGluZ3MkdW1hcApuMSA8LSBuY29uJHBsb3RHcmFwaChncm91cHM9dHlwZWZjLHBhbGV0dGU9dHlwZWZjLnBhbCxhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9VCxwbG90Lm5hPUYpCm4yIDwtIG5jb24kcGxvdEdyYXBoKGdyb3Vwcz10eXBlZixhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9VCxwbG90Lm5hPUYsZm9udC5zaXplPWMoMyw1KSkKI24zIDwtIG5jb24kcGxvdEdyYXBoKGdyb3Vwcz1uY29uJGdldERhdGFzZXRQZXJDZWxsKCksYWxwaGE9YWxwaGEsc2l6ZT1zaXplLG1hcmsuZ3JvdXBzPUYscGxvdC5uYT1GKQpuMyA8LSBuY29uJHBsb3RHcmFwaChhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9VCxwbG90Lm5hPUYpCnBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHAxLG4xLG4yLG4zKSxucm93PTIpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTN9CmFscGhhIDwtIDAuMTsgc2l6ZSA8LSAwLjM7IApuMSA8LSBuY29uJHBsb3RHcmFwaChncm91cHM9dHlwZWZjLHBhbGV0dGU9dHlwZWZjLnBhbCxhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9VCxwbG90Lm5hPUYsZm9udC5zaXplPWMoMyw0KSxyYXN0ZXI9VCxyYXN0ZXIud2lkdGg9MyxyYXN0ZXIuaGVpZ2h0PTMpCnBkZihmaWxlPSd0dW1vci5ub21ldGFiLmVtYmVkZGluZy5wZGYnLHdpZHRoPTMsaGVpZ2h0PTMpOyBwcmludChuMSk7IGRldi5vZmYoKTsKbjEKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9Nn0KYWxwaGEgPC0gMC4yOyBzaXplIDwtIDAuMTsgCiNuMSA8LSBuY29uJHBsb3RHcmFwaChncm91cHM9dHlwZWZjLHBhbGV0dGU9dHlwZWZjLnBhbCxhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9VCxwbG90Lm5hPUYpCm4yIDwtIG5jb24kcGxvdEdyYXBoKGdyb3Vwcz10eXBlZixhbHBoYT1hbHBoYSxzaXplPXNpemUsbWFyay5ncm91cHM9VCxwbG90Lm5hPUYsZm9udC5zaXplPWMoMyw0KSkKbjMgPC0gbmNvbiRwbG90R3JhcGgoZ2VuZT0nS0xLMicsYWxwaGE9YWxwaGEsc2l6ZT1zaXplLG1hcmsuZ3JvdXBzPUYscGxvdC5uYT1GKQpwbG90X2dyaWQocGxvdGxpc3Q9bGlzdChuMixuMyksbnJvdz0xKQpgYGAKCgpDb250cmFzdCB0dW1vciBjbHVzdGVyIGFnYWluc3QgcHJvZ2VuaXRvciBjbHVzdGVyIDM4CmBgYHtyfQoKeCA8LSBuY29uJGNsdXN0ZXJzW1sxXV0kZ3JvdXBzCnR1bW9yLmNlbGxzIDwtIG5hbWVzKHgpW3g9PTQyXQpwcm9nLmNlbGxzIDwtIG5hbWVzKHgpW3g9PTM4XQoKCnVuZmFjIDwtIHR5cGVmY1tkb3VibGV0LmZbbmFtZXModHlwZWZjKV08PTAuMjVdCnVuZmFjW25hbWVzKHVuZmFjKSVpbiUgdHVtb3IuY2VsbHNdIDwtICdUdW1vcicKdW5mYWMgPC0gYXMuZmFjdG9yKHVuZmFjKQoKCgp0ZmFjIDwtIG5jb24kZ2V0RGF0YXNldFBlckNlbGwoKQp0ZmFjIDwtIHRmYWNbIW5hbWVzKHRmYWMpICVpbiUgcHJvZy5jZWxsc10KdGZhYyA8LSBhcy5mYWN0b3Ioc2V0TmFtZXMoaWZlbHNlKG5hbWVzKHRmYWMpICVpbiUgdHVtb3IuY2VsbHMsJ3R1bW9yJywnb3RoZXInKSxuYW1lcyh0ZmFjKSkpCgp0dW1vci5kZSA8LSBuY29uJGdldERpZmZlcmVudGlhbEdlbmVzKGdyb3Vwcz10ZmFjLG4uY29yZXM9MzAsYXBwZW5kLmF1Yz1UUlVFLHoudGhyZXNob2xkPTAsdXByZWd1bGF0ZWQub25seT1UKQoKCnBmYWMgPC0gbmNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpCnBmYWMgPC0gcGZhY1shbmFtZXMocGZhYykgJWluJSB0dW1vci5jZWxsc10KcGZhYyA8LSBhcy5mYWN0b3Ioc2V0TmFtZXMoaWZlbHNlKG5hbWVzKHBmYWMpICVpbiUgcHJvZy5jZWxscywncHJvZycsJ290aGVyJyksbmFtZXMocGZhYykpKQoKcHJvZy5kZSA8LSBuY29uJGdldERpZmZlcmVudGlhbEdlbmVzKGdyb3Vwcz1wZmFjLG4uY29yZXM9MzAsYXBwZW5kLmF1Yz1UUlVFLHoudGhyZXNob2xkPTAsdXByZWd1bGF0ZWQub25seT1UKQoKCmNmYWMgPC0gbmNvbiRnZXREYXRhc2V0UGVyQ2VsbCgpCmNmYWMgPC0gYXMuZmFjdG9yKHNldE5hbWVzKGlmZWxzZShuYW1lcyhjZmFjKSAlaW4lIGMocHJvZy5jZWxscyx0dW1vci5jZWxscyksJ2NvbWInLCdvdGhlcicpLG5hbWVzKGNmYWMpKSkKCmNvbWIuZGUgPC0gbmNvbiRnZXREaWZmZXJlbnRpYWxHZW5lcyhncm91cHM9Y2ZhYyxuLmNvcmVzPTMwLGFwcGVuZC5hdWM9VFJVRSx6LnRocmVzaG9sZD0wLHVwcmVndWxhdGVkLm9ubHk9VCkKCiMgdHJ5IHdpdGggdGhlIGVudGlyZSBwcm9nZW5pdG9yIHBvcHVsYXRpb24sIGJhbGFuY2luZyBzaXplcwpjZmFjIDwtIG5jb24kZ2V0RGF0YXNldFBlckNlbGwoKQp4IDwtIG5hbWVzKHR5cGVmYylbdHlwZWZjPT0iUHJvZ2VuaXRvcnMiXTsKeHMgPC0gc2FtcGxlKHgsbGVuZ3RoKHR1bW9yLmNlbGxzKSkKcGZhYyA8LSBwZmFjWyFuYW1lcyhwZmFjKSAlaW4lIHNldGRpZmYoeCx4cyldCmNmYWMgPC0gYXMuZmFjdG9yKHNldE5hbWVzKGlmZWxzZShuYW1lcyhjZmFjKSAlaW4lIGMoeHMsdHVtb3IuY2VsbHMpLCdjb21iJywnb3RoZXInKSxuYW1lcyhjZmFjKSkpCgpjb21iLmRlMiA8LSBuY29uJGdldERpZmZlcmVudGlhbEdlbmVzKGdyb3Vwcz1jZmFjLG4uY29yZXM9MzAsYXBwZW5kLmF1Yz1UUlVFLHoudGhyZXNob2xkPTAsdXByZWd1bGF0ZWQub25seT1UKQoKCmBgYAoKClRvcCB0dW1vciBtYXJrZXJzCmBgYHtyfQpyZXF1aXJlKGRwbHlyKQp0ZGUgPC0gYXJyYW5nZSh0dW1vci5kZSR0dW1vcixieT1kZXNjKEFVQykpCnRkZQpgYGAKCmBgYHtyfQpwZGUgPC0gYXJyYW5nZShwcm9nLmRlJHByb2csYnk9ZGVzYyhBVUMpKQpwZGUKYGBgCgoKYGBge3J9CmFycmFuZ2UoY29tYi5kZSRjb21iLGJ5PWRlc2MoQVVDKSkKYGBgCgpgYGB7cn0KYXJyYW5nZShjb21iLmRlMiRjb21iLGJ5PWRlc2MoQVVDKSkKYGBgCgpgYGB7cn0Kd3JpdGUoYXMuY2hhcmFjdGVyKGhlYWQoY29tYi5kZTIkY29tYiwzMDApJEdlbmUpLGZpbGU9J2NvbWIyLnRvcDMwMC50eHQnLHNlcD0nXG4nKQp3cml0ZSh1bmlxdWUodW5saXN0KGxhcHBseShjb21iLmRlMixmdW5jdGlvbih4KSBhcy5jaGFyYWN0ZXIoeCRHZW5lKSkpKSxmaWxlPSdiYWNrZ3JvdW5kLnR4dCcsc2VwPSdcbicpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD01LCBmaWcuaGVpZ2h0PTV9CmFscGhhIDwtIDAuMTsgc2l6ZSA8LSAwLjI7IApzY29uJHBsb3RHcmFwaChnZW5lPSdCTE5LJyxhbHBoYT0wLjQsc2l6ZT1zaXplLG1hcmsuZ3JvdXBzPVQscGxvdC5uYT1GKQpgYGAKCgpMb29rIGF0IHRoZSBpbnRlcnNlY3RpbmcgZ2VuZXMKYGBge3J9CnRkZSA8LSBhcnJhbmdlKHR1bW9yLmRlJHR1bW9yLGJ5PWRlc2MoWikpCnBkZSA8LSBhcnJhbmdlKHByb2cuZGUkcHJvZyxieT1kZXNjKFopKQp4IDwtIGludGVyc2VjdChoZWFkKHRkZSw1MDApJEdlbmUsaGVhZChwZGUsNTAwKSRHZW5lKQp3cml0ZSh4LGZpbGU9J2ludGVyc2VjdC50eHQnLHNlcD0nXG4nKQpgYGAKCgoKCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CmFscGhhIDwtIDAuMTsgc2l6ZSA8LSAwLjI7IApuY29uJGVtYmVkZGluZyA8LSBuY29uJG1pc2MkZW1iZWRkaW5ncyR1bWFwCm4xIDwtIHNjb24kcGxvdEdyYXBoKGdyb3Vwcz11bmZhYyxwYWxldHRlPXR5cGVmYy5wYWwsYWxwaGE9YWxwaGEsc2l6ZT1zaXplLG1hcmsuZ3JvdXBzPVQscGxvdC5uYT1GKQpuMQpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xMH0KcGwgPC0gbGFwcGx5KGdyZXAoIlR1bW9yIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKSxmdW5jdGlvbihuYW0pIHsKICBzY2NvcmU6OmVtYmVkZGluZ1Bsb3QobmNvbiRzYW1wbGVzW1tuYW1dXSRlbWJlZGRpbmdzJFBDQSR0U05FLGdyb3Vwcz11bmZhYyxwYWxldHRlPXR5cGVmYy5wYWwscGxvdC5uYT1GLHJhc3Rlcj1ULHJhc3Rlci5oZWlnaHQ9MyxyYXN0ZXIud2lkdGg9Myxmb250LnNpemU9YygzLDQpKSArIGdndGl0bGUobmFtKSArdGhlbWVfYncoKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKyB0aGVtZShheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLCBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCkscGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgcGFuZWwuZ3JpZC5taW5vcj1lbGVtZW50X2JsYW5rKCkpCn0pCnBsb3RfZ3JpZChwbG90bGlzdD1wbCxucm93PTMpCmBgYAoKCgojIyBMb29rIGF0IEVsaSdzIGdlbmVzCgpgYGB7cn0KcXBsb3QgPC0gZnVuY3Rpb24oZywgY29uLm9iaj1jb24sIGFubj1jb24kY2x1c3RlcnMkbGVpZGVuJGdyb3VwcyApIHsKICAjY2F0KGcsJyAnKQogIHggPC0gbGFwcGx5KGNvbi5vYmokc2FtcGxlc1shZ3JlcGwoIldob2xlfE5vbmludiIsbmFtZXMoY29uLm9iaiRzYW1wbGVzKSldLGZ1bmN0aW9uKHIpIHsgaWYoZyAlaW4lIGNvbG5hbWVzKHIkY291bnRzKSkgeyByJGNvdW50c1ssZ10gfSBlbHNlIHsgcmV0dXJuKE5VTEwpIH0gfSkKICBpZihsZW5ndGgodW5saXN0KHgpKTwxKSBzdG9wKCdnZW5lICcsZywnIGlzIG5vdCBmb3VuZCcpCiAgZGYgPC0gZGF0YS5mcmFtZSh2YWw9dW5saXN0KHgpLGNlbGw9dW5saXN0KGxhcHBseSh4LG5hbWVzKSkpCiAgZGYkY2x1c3RlciA8LSBhbm5bbWF0Y2goZGYkY2VsbCxuYW1lcyhhbm4pKV0KICBkZiA8LSBuYS5vbWl0KGRmKQogIAogIG12IDwtIG1heCh0YXBwbHkoZGYkdmFsLGRmJGNsdXN0ZXIscXVhbnRpbGUscD0wLjgpLHRhcHBseShkZiR2YWwsZGYkY2x1c3RlcixtZWFuKSkqMS41CiAgcCA8LSBnZ3Bsb3QoZGYsYWVzKHg9Y2x1c3Rlcix5PXZhbCxjb2xvcj1jbHVzdGVyKSkrZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkrIHN0YXRfc3VtbWFyeShmdW4uZGF0YT1tZWFuX3NlLGdlb209InBvaW50cmFuZ2UiLCBjb2xvcj0iYmxhY2siKSt5bGFiKGcpK2dndGl0bGUoZykrZ3VpZGVzKGNvbG91cj1GQUxTRSkrIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSx2anVzdD0wLjUpKStjb29yZF9jYXJ0ZXNpYW4oeWxpbT1jKDAsbXYpKTsKICBwCn0KYGBgCgoKYGBge3IgZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9MjV9CmVsaS5nZW5lcyA8LSBjKCJDWENSNCIsJ0daTUInLCdHWk1BJywnUERDRDEnLCdIQVZDUjInLCdUSUdJVCcsJ0xBRzMnLCdUQ0Y3JywnRU5UUEQxJywnR05MWScsJ0NYM0NSMScsJ1BSRjEnLCdTTEFNRjYnKQpwbCA8LSBsYXBwbHkoZWxpLmdlbmVzLHFwbG90LGNvbi5vYmo9c2Nvbixhbm49dHlwZWYpCmNvd3Bsb3Q6OnBsb3RfZ3JpZChwbG90bGlzdD1wbCxuY29sPTIpCmBgYAoKCgpgYGB7cn0KcXBsb3QoJ1RHRkIyJyxzY29uLGFubj10eXBlZikKYGBgCgoKIyMjIEV4dGVuZGVkIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIHRlc3RzCgpFeHRlbmQgdGhlIGdldFBlckNlbGxUeXBlREUgZnVuY3Rpb24gdG8gcGVyZm9ybSBwZXItc2FtcGxlIFdpbGNveG9uIHRlc3QsIGJvb3RzdHJhcCByZXNhbXBsZWQgdGVzdCwgYXMgd2VsbCBhcyBhIGNvbWJpbmVkIFdpbGNveG9uIHRlc3QuCgpgYGB7cn0KCiMgZ2l2ZW4gYSB2ZWN0b3Igb2YgWiBzY29yZXMgZnJvbSByZXNhbXBsaWduIHJvdW5kcywgcmV0dXJucyBhIGNvbnNlcnZhdGl2ZSBsb3dlciBib3VuZCBvZiB0aGUgWiBzY29yZSB1c2luZyBhIHNwZWNpZmllZCBxdWFudGlsZSAocmVwcm9kdWNpYmlsaXR5IHBvd2VyKQpjb25zZXJ2YXRpdmUuWiA8LSBmdW5jdGlvbih6LCBxdWFudGlsZSkgewogIGJxIDwtIGFzLm51bWVyaWMoc29ydChxdWFudGlsZSh6LHA9YyhxdWFudGlsZSwxLXF1YW50aWxlKSkpKQogIGlmKHNpZ24oYnFbMV0pIT1zaWduKGJxWzJdKSkgcmV0dXJuKDApICMgY29uZmlkZW5jZSBpbnRlcnZhbCBpbmNsdWRlcyAwCiAgcmV0dXJuKGJxW3doaWNoLm1pbihhYnMoYnEpKV0pCn0KCiMgZ2l2ZW4gYSB2ZWN0b3Igb2YgcC12YWx1ZXMgZnJvbSByZXNhbXBsaW5nIHJvdW5kcywgcmV0dXJucyBhbiB1cHBlciBib3VuZCBiYXNlZCBvbiB0aGUgZGVzaXJlZCBxdWFudGlsZQpjb25zZXJ2YXRpdmUucHZhbCA8LSBmdW5jdGlvbihwdmFsLCBxdWFudGlsZSkgewogIGFzLm51bWVyaWMobWF4KHF1YW50aWxlKHB2YWwscD1jKHF1YW50aWxlLDEtcXVhbnRpbGUpKSkpCn0KCiMgY20gLSBleHByZXNzaW9uIG1hdHJpeCB3aXRoIHJvd3MgYmVpbmcgY2VsbHMKIyBjb2xzIC0gYSBmYWN0b3Igb24gdGhlIHJvd3MgKG11c3QgbWF0Y2ggb3JkZXIpCiMgcmV0dXJuOiByYXcgYW5kIGFkanVzdGVkIFogc2NvcmVzCnJvd1dpc2VXaWxjb3hvblRlc3QgPC0gZnVuY3Rpb24oY20sIGNvbHMsIGxvd2VyLmxwdi5saW1pdD0tMTAwKSB7CiAgIyBydW4gd2lsY294b24gdGVzdCBjb21wYXJpbmcgZWFjaCBncm91cCB3aXRoIHRoZSByZXN0CiAgIyBjYWxjdWxhdGUgcmFuayBwZXItY29sdW1uIChwZXItZ2VuZSkgYXZlcmFnZSByYW5rIG1hdHJpeAogIHhyIDwtIHBhZ29kYTI6OjpzcGFyc2VfbWF0cml4X2NvbHVtbl9yYW5rcyhjbSk7CiAgIyBjYWxjdWxhdGUgcmFuayBzdW1zIHBlciBncm91cAogIGdycyA8LSBwYWdvZGEyOjo6Y29sU3VtQnlGYWMoeHIsYXMuaW50ZWdlcihjb2xzKSlbLTEsLGRyb3A9Rl0KICAjIGNhbGN1bGF0ZSBudW1iZXIgb2Ygbm9uLXplcm8gZW50cmllcyBwZXIgZ3JvdXAKICB4ckB4IDwtIG51bWVyaWMobGVuZ3RoKHhyQHgpKSsxCiAgZ256eiA8LSBwYWdvZGEyOjo6Y29sU3VtQnlGYWMoeHIsYXMuaW50ZWdlcihjb2xzKSlbLTEsLGRyb3A9Rl0KICAjZ3JvdXAuc2l6ZSA8LSBhcy5udW1lcmljKHRhcHBseShjb2xzLGNvbHMsbGVuZ3RoKSk7CiAgZ3JvdXAuc2l6ZSA8LSBhcy5udW1lcmljKHRhcHBseShjb2xzLGNvbHMsbGVuZ3RoKSlbMTpucm93KGduenopXTsgZ3JvdXAuc2l6ZVtpcy5uYShncm91cC5zaXplKV08LTA7ICMgdHJhaWxpbmcgZW1wdHkgbGV2ZWxzIGFyZSBjdXQgb2ZmIGJ5IGNvbFN1bUJ5RmFjCiAgIyBhZGQgY29udHJpYnV0aW9uIG9mIHplcm8gZW50cmllcyB0byB0aGUgZ3JzCiAgZ256IDwtIChncm91cC5zaXplLWduenopCiAgIyByYW5rIG9mIGEgMCBlbnRyeSBmb3IgZWFjaCBnZW5lCiAgemVyby5yYW5rcyA8LSAobnJvdyh4ciktZGlmZih4ckBwKSsxKS8yICMgbnVtYmVyIG9mIHRvdGFsIHplcm8gZW50cmllcyBwZXIgZ2VuZQogIHVzdGF0IDwtIHQoKHQoZ256KSp6ZXJvLnJhbmtzKSkgKyBncnMgLSBncm91cC5zaXplKihncm91cC5zaXplKzEpLzIKICAjIHN0YW5kYXJkaXplCiAgbjFuMiA8LSBncm91cC5zaXplKihucm93KGNtKS1ncm91cC5zaXplKTsKICAjIHVzaWdtYSA8LSBzcXJ0KG4xbjIqKG5yb3coY20pKzEpLzEyKSAjIHdpdGhvdXQgdGllIGNvcnJlY3Rpb24KICAjIGNvcnJlY3RpbmcgZm9yIDAgdGllcywgb2Ygd2hpY2ggdGhlcmUgYXJlIHBsZW50eQogIHVzaWdtYSA8LSBzcXJ0KG4xbjIqKG5yb3coY20pKzEpLzEyKQogIHVzaWdtYSA8LSBzcXJ0KChucm93KGNtKSArMSAtIChnbnpeMyAtIGdueikvKG5yb3coY20pKihucm93KGNtKS0xKSkpKm4xbjIvMTIpCiAgeiA8LSB0KCh1c3RhdCAtIG4xbjIvMikvdXNpZ21hKTsgIyBzdGFuZGFyZGl6ZWQgVSB2YWx1ZS0geiBzY29yZQoKICBjeiA8LSBtYXRyaXgocW5vcm0ocGFnb2RhMjo6OmJoLmFkanVzdChwbm9ybShhcy5udW1lcmljKGFicyh6KSksIGxvd2VyLnRhaWwgPSBGQUxTRSwgbG9nLnAgPSBUUlVFKSwgbG9nID0gVFJVRSksIGxvd2VyLnRhaWwgPSBGQUxTRSwgbG9nLnAgPSBUUlVFKSxuY29sPW5jb2woeikpKnNpZ24oeikKICByb3duYW1lcyh6KSA8LSByb3duYW1lcyhjeikgPC0gY29sbmFtZXMoY20pOyBjb2xuYW1lcyh6KSA8LSBjb2xuYW1lcyhjeikgPC0gbGV2ZWxzKGNvbHMpWzE6bmNvbCh6KV07CiAgcmV0dXJuKGxpc3Qoej16LGN6PWN6KSkKfQoKIycgRG8gZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gZm9yIGVhY2ggY2VsbCB0eXBlIGluIGEgY29ub3Mgb2JqZWN0IGJldHdlZW4gdGhlIHNwZWNpZmllZCBzdWJzZXRzIG9mIGFwcHMKIycgQHBhcmFtIGNvbi5vYmogY29ub3Mgb2JqZWN0CiMnIEBwYXJhbSBncm91cHMgZmFjdG9yIHNwZWNpZnlpbmcgY2VsbCB0eXBlcwojJyBAcGFyYW0gc2FtcGxlLmdyb3VwcyBhIGxpc3Qgb2YgdHdvIGNoYXJhY3RlciB2ZWN0b3Igc3BlY2lmeWluZyB0aGUgYXBwIGdyb3VwcyB0byBjb21wYXJlCiMnIEBwYXJhbSBjb29rcy5jdXRvZmYgY29va3NDdXRvZmYgZm9yIERFU2VxMgojJyBAcGFyYW0gaW5kZXBlbmRlbnQuZmlsdGVyaW5nIGluZGVwZW5kZW50RmlsdGVyaW5nIGZvciBERVNlcTIKIycgQHBhcmFtIG4uY29yZXMgbnVtYmVyIG9mIGNvcmVzCiMnIEBwYXJhbSBjbHVzdGVyLnNlcC5jaHIgY2hhcmFjdGVyIHN0cmluZyBvZiBsZW5ndGggMSBzcGVjaWZ5aW5nIGEgZGVsaW1pdGVyIHRvIHNlcGFyYXRlIGNsdXN0ZXIgYW5kIGFwcCBuYW1lcwojJyBAcGFyYW0gcmV0dXJuLmRldGFpbHMgcmV0dXJuIGRldGFscwojJyBAZXhwb3J0IGdldFBlckNlbGxUeXBlREUKZ2V0UGVyQ2VsbFR5cGVERTIgPC0gZnVuY3Rpb24oY29uLm9iaiwgZ3JvdXBzPU5VTEwsIHNhbXBsZS5ncm91cHM9TlVMTCwgY29va3MuY3V0b2ZmID0gRkFMU0UsIHJlZi5sZXZlbCA9IE5VTEwsIG1pbi5jZWxsLmNvdW50ID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaW5kZXBlbmRlbnQuZmlsdGVyaW5nID0gRkFMU0UsIG4uY29yZXM9MSwgY2x1c3Rlci5zZXAuY2hyID0gJzwhIT4nLHJldHVybi5kZXRhaWxzPVRSVUUsIG4uYm9vdHN0cmFwcz0wLCBib290c3RyYXAucXVhbnRpbGU9MC45KSB7CiAgY29ub3M6Ojp2YWxpZGF0ZVBlckNlbGxUeXBlUGFyYW1zKGNvbi5vYmosIGdyb3Vwcywgc2FtcGxlLmdyb3VwcywgcmVmLmxldmVsLCBjbHVzdGVyLnNlcC5jaHIpCgogICMjIEdlbmVyYXRlIGEgc3VtbWFyeSBkYXRhc2V0IGNvbGxhcHNpbmcgdGhlIGNlbGxzIG9mIHRoZSBzYW1lIHR5cGUgaW4gZWFjaCBzYW1wbGUKICAjIyBhbmQgbWVyZ2luZyBldmVyeXRoaW5nIGluIG9uZSBtYXRyaXgKICBjbWwgPC0gY29ub3M6OjpyYXdNYXRyaWNlc1dpdGhDb21tb25HZW5lcyhjb24ub2JqLCBzYW1wbGUuZ3JvdXBzKQogIGFnZ3IyIDwtIGNtbCAlPiUgbGFwcGx5KGNvbm9zOjo6Y29sbGFwc2VDZWxsc0J5VHlwZSwgZ3JvdXBzPWdyb3VwcywgbWluLmNlbGwuY291bnQ9bWluLmNlbGwuY291bnQpICU+JSBjb25vczo6OnJiaW5kREVNYXRyaWNlcyhjbHVzdGVyLnNlcC5jaHI9Y2x1c3Rlci5zZXAuY2hyKQogIAogICMgc2FtcGxlIGdyb3VwcyBmYWN0b3IKICBzYW1wbGUuZ2ZhYyA8LSBzZXROYW1lcyhyZXAobmFtZXMoc2FtcGxlLmdyb3VwcyksdW5saXN0KGxhcHBseShzYW1wbGUuZ3JvdXBzLGxlbmd0aCkpKSx1bmxpc3Qoc2FtcGxlLmdyb3VwcykpCiAgIyBjZWxsIHNhbXBsZSBmYWN0b3IKICBzYW1mIDwtIHNldE5hbWVzKHJlcChuYW1lcyhjbWwpLHVubGlzdChsYXBwbHkoY21sLG5yb3cpKSksdW5saXN0KGxhcHBseShjbWwscm93bmFtZXMpKSkKICAjIGNlbGwgZ3JvdXBzIGZhY3RvcgogIGNlbGwuZ2ZhYyA8LSBzZXROYW1lcyhzYW1wbGUuZ2ZhY1tzYW1mXSxuYW1lcyhzYW1mKSkKICAKICAjIyBGb3IgZXZlcnkgY2VsbCB0eXBlIGdldCBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiByZXN1bHRzCiAgZGUucmVzIDwtIGNvbm9zOjo6cGFwcGx5KGNvbm9zOjo6c24obGV2ZWxzKGdyb3VwcykpLCBmdW5jdGlvbihsKSB7CiAgICB0cnlDYXRjaCh7CiAgICAgICMjIEdldCBjb3VudCBtYXRyaXgKICAgICAgY20gPC0gYWdncjJbLHN0cnBhcnQoY29sbmFtZXMoYWdncjIpLGNsdXN0ZXIuc2VwLmNociwyLGZpeGVkPVRSVUUpID09IGxdCiAgICAgICMjIEdlbmVyYXRlIG1ldGFkYXRhCiAgICAgIG1ldGEgPC0gZGF0YS5mcmFtZSgKICAgICAgICBzYW1wbGUuaWQ9IGNvbG5hbWVzKGNtKSwKICAgICAgICBncm91cD0gYXMuZmFjdG9yKHVubGlzdChsYXBwbHkoY29sbmFtZXMoY20pLCBmdW5jdGlvbih5KSB7CiAgICAgICAgICB5IDwtIHN0cnBhcnQoeSxjbHVzdGVyLnNlcC5jaHIsMSxmaXhlZD1UUlVFKQogICAgICAgICAgbmFtZXMoc2FtcGxlLmdyb3VwcylbdW5saXN0KGxhcHBseShzYW1wbGUuZ3JvdXBzLGZ1bmN0aW9uKHgpIGFueSh4ICVpbiUgeSkpKV0KICAgICAgICB9KSkpCiAgICAgICkKICAgICAgaWYgKCFyZWYubGV2ZWwgJWluJSBsZXZlbHMobWV0YSRncm91cCkpCiAgICAgICAgc3RvcCgnVGhlIHJlZmVyZW5jZSBsZXZlbCBpcyBhYnNlbnQgaW4gdGhpcyBjb21wYXJpc29uJykKICAgICAgbWV0YSRncm91cCA8LSByZWxldmVsKG1ldGEkZ3JvdXAsIHJlZj1yZWYubGV2ZWwpCiAgICAgIGlmIChsZW5ndGgodW5pcXVlKGFzLmNoYXJhY3RlcihtZXRhJGdyb3VwKSkpIDwgMikKICAgICAgICBzdG9wKCdUaGUgY2x1c3RlciBpcyBub3QgcHJlc2VudCBpbiBib3RoIGNvbmRpdGlvbnMnKQogICAgICBkZHMxIDwtIERFU2VxMjo6REVTZXFEYXRhU2V0RnJvbU1hdHJpeChjbSxtZXRhLGRlc2lnbj1+Z3JvdXApCiAgICAgIGRkczEgPC0gREVTZXEyOjpERVNlcShkZHMxKQogICAgICByZXMxIDwtIERFU2VxMjo6cmVzdWx0cyhkZHMxLCBjb29rc0N1dG9mZiA9IGNvb2tzLmN1dG9mZiwgaW5kZXBlbmRlbnRGaWx0ZXJpbmcgPSBpbmRlcGVuZGVudC5maWx0ZXJpbmcpCiAgICAgIHJlczEgPC0gYXMuZGF0YS5mcmFtZShyZXMxKQogICAgICAKICAgICAgCiAgICAgICMgZmlsdGVyIGNtbCB0byBsZWF2ZSBvbmx5IHRoZSBjZWxscyBvZiB0aGF0IHR5cGUgYW5kIGRyb3AgdGhlIHNhbXBsZXMgd2l0aCBpbnN1ZmZpY2llbnQgY2VsbHMKICAgICAgY21sZiA8LSBsYXBwbHkoY21sLGZ1bmN0aW9uKGNtKSB7CiAgICAgICAgdmMgPC0gaW50ZXJzZWN0KHJvd25hbWVzKGNtKSwgbmFtZXMoZ3JvdXBzKVtncm91cHM9PWxdKQogICAgICAgIGlmKGxlbmd0aCh2Yyk8bWluLmNlbGwuY291bnQpIHJldHVybihOVUxMKQogICAgICAgIGNtW3ZjLCxkcm9wPUZdCiAgICAgIH0pCiAgICAgIGNtbGYgPC0gY21sZlshdW5saXN0KGxhcHBseShjbWxmLGlzLm51bGwpKV0KICAgICAgCiAgICAgICMgZGF0YXNldC13aXNlIHdpbGNveG9uIHRlc3QKICAgICAgeCA8LSByb3dXaXNlV2lsY294b25UZXN0KGFzKHQoY20pLCdkZ0NNYXRyaXgnKSwgYXMuZmFjdG9yKG1ldGEkZ3JvdXApKQogICAgICByZXMxJHdaIDwtIC14JHpbLHJlZi5sZXZlbF0KICAgICAgcmVzMSR3WmFkaiA8LSAteCRjelsscmVmLmxldmVsXQogICAgICAjcmVzMSR3UCA8LSBwbWluKHBub3JtKHgkelsscmVmLmxldmVsXSkscG5vcm0oeCR6WyxyZWYubGV2ZWxdLGxvd2VyLnRhaWw9RikpCiAgICAgICNyZXMxJHdQYWRqIDwtIHBtaW4ocG5vcm0oeCRjelsscmVmLmxldmVsXSkscG5vcm0oeCRjelsscmVmLmxldmVsXSxsb3dlci50YWlsPUYpKQogICAgICAKICAgICAgIyByZXNhbXBsZWQgZGF0YXNldC13aXNlIHdpbGNveG9uIHRlc3RzCiAgICAgIGlmKG4uYm9vdHN0cmFwcz4wKSB7CiAgICAgICAgeHggPC0gbGFwcGx5KDE6bi5ib290c3RyYXBzLGZ1bmN0aW9uKGkpIHsKICAgICAgICAgIGNtIDwtIGRvLmNhbGwocmJpbmQsbGFwcGx5KGNtbGYsZnVuY3Rpb24oeCkgY29sU3Vtcyh4W3NhbXBsZShyb3duYW1lcyh4KSxyZXBsYWNlID0gVCksXSkpKQogICAgICAgICAgcm93V2lzZVdpbGNveG9uVGVzdChhcyhjbSwnZGdDTWF0cml4JyksIGFzLmZhY3RvcihzYW1wbGUuZ2ZhY1tyb3duYW1lcyhjbSldKSkKICAgICAgICB9KQogICAgICAgIHh4IDwtIGRvLmNhbGwocmJpbmQsbGFwcGx5KHh4LGZ1bmN0aW9uKHgpIHgkelsscmVmLmxldmVsXSkpCiAgICAgICAgCiAgICAgICAgcmVzMSRid1ogPC0gLWFwcGx5KHh4LDIsY29uc2VydmF0aXZlLloscXVhbnRpbGU9Ym9vdHN0cmFwLnF1YW50aWxlKQogICAgICAgIAogICAgICAgIHJlczEkYndaYWRqIDwtIHFub3JtKHBhZ29kYTI6OjpiaC5hZGp1c3QocG5vcm0oYXMubnVtZXJpYyhhYnMocmVzMSRid1opKSwgbG93ZXIudGFpbCA9IEZBTFNFLCBsb2cucCA9IFRSVUUpLCBsb2cgPSBUUlVFKSwgbG93ZXIudGFpbCA9IEZBTFNFLCBsb2cucCA9IFRSVUUpKnNpZ24ocmVzMSRid1opCiAgICAgIH0KICAgICAgCiAgICAgICMgY2VsbC13aXNlIHdpbGNveG9uIHRlc3QKICAgICAgY20yIDwtIGRvLmNhbGwocmJpbmQsY21sZikKICAgICAgeCA8LSByb3dXaXNlV2lsY294b25UZXN0KGNtMiwgYXMuZmFjdG9yKGNlbGwuZ2ZhY1tyb3duYW1lcyhjbTIpXSkpCiAgICAgICMgcmVzMSRjd1AgPC0gcG1pbihwbm9ybSh4JHpbLHJlZi5sZXZlbF0pLHBub3JtKHgkelsscmVmLmxldmVsXSxsb3dlci50YWlsPUYpKQogICAgICAjIHJlczEkY3dQYWRqIDwtIHBtaW4ocG5vcm0oeCRjelsscmVmLmxldmVsXSkscG5vcm0oeCRjelsscmVmLmxldmVsXSxsb3dlci50YWlsPUYpKQogICAgICByZXMxJGN3WiA8LSAteCR6WyxyZWYubGV2ZWxdCiAgICAgIHJlczEkY3daYWRqIDwtIC14JGN6WyxyZWYubGV2ZWxdCiAgICAgIAogICAgICByZXMxIDwtIHJlczFbb3JkZXIocmVzMSRwYWRqLGRlY3JlYXNpbmcgPSBGQUxTRSksXQogICAgICAgICAgCiAgICAgIGdjKCkKICAgICAgIyMKICAgICAgaWYocmV0dXJuLmRldGFpbHMpIHsKICAgICAgICBsaXN0KHJlcz1yZXMxLCBjbT1jbSwgc2FtcGxlLmdyb3Vwcz1zYW1wbGUuZ3JvdXBzKQogICAgICB9IGVsc2UgewogICAgICAgIHJlczEKICAgICAgfQogICAgfSwgZXJyb3I9ZnVuY3Rpb24oZXJyKSB7d2FybmluZygiRXJyb3IgZm9yIGxldmVsICIsIGwsICI6ICIsIGVyciRtZXNzYWdlKTsgcmV0dXJuKE5BKX0pCiAgfSwgbi5jb3Jlcz1uLmNvcmVzKQogIAogIGRlLnJlcwp9CmBgYAoKCgpgYGB7cn0Kc2FtcGxlZ3JvdXBzIDwtIGxpc3QoCiAgQmVuaWduID0gZ3JlcCgiLVdob2xlIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKSwKICBUdW1vciA9IGdyZXAoIi1UdW1vciIsbmFtZXMoc2NvbiRzYW1wbGVzKSx2YWw9VCkKKQoKZGUuaW5mbzIgIDwtIGdldFBlckNlbGxUeXBlREUyKHNjb24sIGdyb3Vwcz1uZmFjLCBzYW1wbGUuZ3JvdXBzID0gc2FtcGxlZ3JvdXBzLCByZWYubGV2ZWw9J0JlbmlnbicsIG4uY29yZXM9MzAsbi5ib290c3RyYXBzPTEwMCkKI3NhdmVSRFMoZGUuaW5mbzIsZmlsZT0nZGUuVEIucmRzJykKYGBgCgoKCldyaXRpbmcgb3V0IEpTT04gZmlsZXMgd2l0aCB0aGUgdXBkYXRlZCB0ZXN0IGluZm86CgpgYGB7cn0Kc291cmNlKCJ+cGtoYXJjaGVua28vbS9wYXZhbi9ETEkvY29ucDIuciIpCmRpci5jcmVhdGUoJ2pzb24nLHNob3dXYXJuaW5ncyA9IEYpCiMgbmVlZCB0byBnZXQgcmlkIG9mIHNwYWNlcyBpbiB0aGUgbmFtZXMKbmFtZXMoZGUuaW5mbzIpIDwtIGdzdWIoIiAiLCJfIixuYW1lcyhkZS5pbmZvMikpCgpzYXZlREVhc0pTT04yKGRlLmluZm8yLCdqc29uLycpCmBgYAoKd3JpdGUgb3V0IGxpbmsgdGFibGU6IG5vdGUgdGhhdCB5b3UnbGwgbmVlZCB0byBjaGFuZ2UgdGhlIGxvY2F0aW9uIHZhcmlhYmxlIHRvIHNwZWNpZnkgd2hlcmUgdGhlIGZvbGRlciB3aXRoIGFsbCB0aGUganNvbiBmaWxlcyBhbmQgZGV2aWV3LjMuaHRtbCAodGhhdCBoYXMgdG8gYmUgY29waWVkIHRoZXJlIG1hbnVhbGx5KSB3aWxsIGJlIGxvY2F0ZWQgb24gdGhlIHNlcnZlcjoKYGBge3J9CiMgcGF0aCAtIGxvY2FsIHBhdGggd2hlcmUgdG8gc2F2ZSB0aGUgVE9DIGZpbGUKIyBsb2NhdGlvbiAtIHdlYiBzZXJ2ZXIgcGF0aCB3aGVyZSB0aGUganNvbiBmaWxlcyBhbmQgdGhlIHRvYyBmaWxlIHdpbGwgcmVzaWRlCndyaXRlLnRvYy5maWxlIDwtIGZ1bmN0aW9uKGRlLmluZm8scGF0aD0nanNvbicsZm5hbWU9J3RvYy5odG1sJyxsb2NhdGlvbj0naHR0cDovL3BrbGFiLm1lZC5oYXJ2YXJkLmVkdS9wZXRlcmsvc2NhZGRlbi9ibW1ldC9zdXBwL2pzb24nKSB7CiAgdG9jLmZpbGUgPC0gcGFzdGUocGF0aCxmbmFtZSxzZXA9Jy8nKQogIHMgPC0gcGFzdGUoYyhsaXN0KCc8aHRtbD48aGVhZD48c3R5bGU+CnRhYmxlIHsKICBmb250LWZhbWlseTogYXJpYWwsIHNhbnMtc2VyaWY7CiAgYm9yZGVyLWNvbGxhcHNlOiBjb2xsYXBzZTsKICB3aWR0aDogMTAwJTsKfQoKdGQsIHRoIHsKICBib3JkZXI6IDFweCBzb2xpZCAjZGRkZGRkOwogIHRleHQtYWxpZ246IGxlZnQ7CiAgcGFkZGluZzogOHB4Owp9Cgp0cjpudGgtY2hpbGQoZXZlbikgewogIGJhY2tncm91bmQtY29sb3I6ICNkZGRkZGQ7Cn0KPC9zdHlsZT48L2hlYWQ+PGJvZHk+PHRhYmxlPicpLApzdXBwcmVzc1dhcm5pbmdzKGxhcHBseShuYW1lcyhkZS5pbmZvKSxmdW5jdGlvbihuKSB7IGlmKCFpcy5uYShkZS5pbmZvW1tuXV0pKSB7IHJlZiA8LSBwYXN0ZTAoJzxhIGhyZWY9IicsbG9jYXRpb24sJy9kZXZpZXcuMy5odG1sP2Q9JyxuLCcuanNvbiI+JyxuLCc8L2E+JykgfSBlbHNlIHtyZWYgPC0gJ05BJ307IHBhc3RlMCgnPHRyPjx0ZD4nLHJlZiwnPC90ZD48L3RyPicpIH0pKSwKbGlzdCgnPC90YWJsZT48L2JvZHk+PC9odG1sPicpKSxjb2xsYXBzZT0nXG4nKQogIHdyaXRlKHMsZmlsZT10b2MuZmlsZSkKfQpgYGAKCgpgYGB7cn0Kd3JpdGUudG9jLmZpbGUoZGUuaW5mbzIscGF0aD0nanNvbicsbG9jYXRpb249J2h0dHA6Ly9wa2xhYi5tZWQuaGFydmFyZC5lZHUvcGV0ZXJrL3NjYWRkZW4vYm1tZXQvc3VwcC9qc29uJykKYGBgCgoKQ2FsY3VsYXRlIGFsbCBjb250cmFzdHM6CmBgYHtyfQpzYW1wbGVncm91cHMgPC0gbGlzdCgKICBCZW5pZ24gPSBncmVwKCItV2hvbGUiLG5hbWVzKHNjb24kc2FtcGxlcyksdmFsPVQpLAogIFR1bW9yID0gZ3JlcCgiLVR1bW9yIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKQopCgpkZS5UQiAgPC0gZ2V0UGVyQ2VsbFR5cGVERTIoc2NvbiwgZ3JvdXBzPW5mYWMsIHNhbXBsZS5ncm91cHMgPSBzYW1wbGVncm91cHMsIHJlZi5sZXZlbD0nQmVuaWduJywgbi5jb3Jlcz0zMCxuLmJvb3RzdHJhcHM9MTAwKQpzYXZlUkRTKGRlLlRCLGZpbGU9J2RlLlRCLnJkcycpCgoKc2FtcGxlZ3JvdXBzIDwtIGxpc3QoCiAgQmVuaWduID0gZ3JlcCgiLVdob2xlIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKSwKICBJbnZvbHZlZCA9IGdyZXAoIi1JbnZvbHZlZCIsbmFtZXMoc2NvbiRzYW1wbGVzKSx2YWw9VCkKKQpkZS5JQiAgPC0gZ2V0UGVyQ2VsbFR5cGVERTIoc2NvbiwgZ3JvdXBzPW5mYWMsIHNhbXBsZS5ncm91cHMgPSBzYW1wbGVncm91cHMsIHJlZi5sZXZlbD0nQmVuaWduJywgbi5jb3Jlcz0zMCxuLmJvb3RzdHJhcHM9MTAwKQoKc2FtcGxlZ3JvdXBzIDwtIGxpc3QoCiAgRGlzdGFsID0gZ3JlcCgiLU5vbmludm9sdmVkIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKSwKICBJbnZvbHZlZCA9IGdyZXAoIi1JbnZvbHZlZCIsbmFtZXMoc2NvbiRzYW1wbGVzKSx2YWw9VCkKKQpkZS5JRCAgPC0gZ2V0UGVyQ2VsbFR5cGVERTIoc2NvbiwgZ3JvdXBzPW5mYWMsIHNhbXBsZS5ncm91cHMgPSBzYW1wbGVncm91cHMsIHJlZi5sZXZlbD0nRGlzdGFsJywgbi5jb3Jlcz0zMCxuLmJvb3RzdHJhcHM9MTAwKQoKCnNhbXBsZWdyb3VwcyA8LSBsaXN0KAogIEludm9sdmVkID0gZ3JlcCgiLUludm9sdmVkIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKSwKICBUdW1vciA9IGdyZXAoIi1UdW1vciIsbmFtZXMoc2NvbiRzYW1wbGVzKSx2YWw9VCkKKQpkZS5USSAgPC0gZ2V0UGVyQ2VsbFR5cGVERTIoc2NvbiwgZ3JvdXBzPW5mYWMsIHNhbXBsZS5ncm91cHMgPSBzYW1wbGVncm91cHMsIHJlZi5sZXZlbD0nSW52b2x2ZWQnLCBuLmNvcmVzPTMwLG4uYm9vdHN0cmFwcz0xMDApCgpzYW1wbGVncm91cHMgPC0gbGlzdCgKICBEaXN0YWwgPSBncmVwKCItTm9uaW52b2x2ZWQiLG5hbWVzKHNjb24kc2FtcGxlcyksdmFsPVQpLAogIFR1bW9yID0gZ3JlcCgiLVR1bW9yIixuYW1lcyhzY29uJHNhbXBsZXMpLHZhbD1UKQopCmRlLlREICA8LSBnZXRQZXJDZWxsVHlwZURFMihzY29uLCBncm91cHM9bmZhYywgc2FtcGxlLmdyb3VwcyA9IHNhbXBsZWdyb3VwcywgcmVmLmxldmVsPSdEaXN0YWwnLCBuLmNvcmVzPTMwLG4uYm9vdHN0cmFwcz0xMDApCgpzYXZlUkRTKGRlLlRCLGZpbGU9J2RlLlRCLnJkcycpCnNhdmVSRFMoZGUuSUIsZmlsZT0nZGUuSUIucmRzJykKc2F2ZVJEUyhkZS5JRCxmaWxlPSdkZS5JRC5yZHMnKQpzYXZlUkRTKGRlLlRJLGZpbGU9J2RlLlRJLnJkcycpCnNhdmVSRFMoZGUuVEQsZmlsZT0nZGUuVEQucmRzJykKYGBgCgpgYGB7cn0KZGlyLmNyZWF0ZSgnanNvbi5UQicsc2hvd1dhcm5pbmdzID0gRikKc2F2ZURFYXNKU09OMihkZS5UQiwnanNvbi5UQi8nKQpkaXIuY3JlYXRlKCdqc29uLklCJyxzaG93V2FybmluZ3MgPSBGKQpzYXZlREVhc0pTT04yKGRlLklCLCdqc29uLklCLycpCmBgYAoKYGBge3J9CndyaXRlLnRvYy5maWxlKGRlLlRCLHBhdGg9J2pzb24uVEInLGxvY2F0aW9uPSdodHRwOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L3BldGVyay9zY2FkZGVuL2JtbWV0L3N1cHAvanNvbi5UQicpCndyaXRlLnRvYy5maWxlKGRlLklCLHBhdGg9J2pzb24uSUInLGxvY2F0aW9uPSdodHRwOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L3BldGVyay9zY2FkZGVuL2JtbWV0L3N1cHAvanNvbi5JQicpCmBgYAoK